@verdaccio/config 7.0.0-next.6 → 8.0.0-next-8.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/README.md +69 -0
  3. package/build/agent.js +1 -1
  4. package/build/agent.js.map +1 -1
  5. package/build/builder.js +2 -3
  6. package/build/builder.js.map +1 -1
  7. package/build/conf/default.yaml +10 -1
  8. package/build/conf/docker.yaml +10 -1
  9. package/build/conf/index.js.map +1 -1
  10. package/build/config-path.d.ts +2 -3
  11. package/build/config-path.js +51 -22
  12. package/build/config-path.js.map +1 -1
  13. package/build/config-utils.js +14 -3
  14. package/build/config-utils.js.map +1 -1
  15. package/build/config.d.ts +13 -5
  16. package/build/config.js +71 -35
  17. package/build/config.js.map +1 -1
  18. package/build/index.js +1 -1
  19. package/build/index.js.map +1 -1
  20. package/build/package-access.js +4 -1
  21. package/build/package-access.js.map +1 -1
  22. package/build/parse.js +1 -1
  23. package/build/parse.js.map +1 -1
  24. package/build/security.js +2 -1
  25. package/build/security.js.map +1 -1
  26. package/build/serverSettings.js.map +1 -1
  27. package/build/token.d.ts +1 -0
  28. package/build/token.js +2 -0
  29. package/build/token.js.map +1 -1
  30. package/build/uplinks.js +1 -1
  31. package/build/uplinks.js.map +1 -1
  32. package/build/user.js.map +1 -1
  33. package/package.json +8 -10
  34. package/src/builder.ts +1 -2
  35. package/src/conf/default.yaml +10 -1
  36. package/src/conf/docker.yaml +10 -1
  37. package/src/config-path.ts +57 -25
  38. package/src/config-utils.ts +13 -2
  39. package/src/config.ts +79 -35
  40. package/src/package-access.ts +4 -0
  41. package/src/security.ts +1 -0
  42. package/src/token.ts +2 -0
  43. package/test/__snapshots__/builder.spec.ts.snap +2 -2
  44. package/test/__snapshots__/config-parsing.spec.ts.snap +2 -2
  45. package/test/agent.spec.ts +2 -0
  46. package/test/builder.spec.ts +55 -0
  47. package/test/config-parsing.spec.ts +1 -0
  48. package/test/config-utils.spec.ts +1 -0
  49. package/test/config.path.spec.ts +102 -74
  50. package/test/config.spec.ts +71 -12
  51. package/test/package-access.spec.ts +1 -0
  52. package/test/token.spec.ts +2 -0
  53. package/test/uplinks.spec.ts +2 -0
  54. package/test/utils.spec.ts +2 -0
  55. package/jest.config.js +0 -9
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { describe, expect, test } from 'vitest';
3
4
 
4
5
  import { fileUtils } from '@verdaccio/core';
5
6
 
@@ -1,4 +1,5 @@
1
1
  import path from 'path';
2
+ import { describe, expect, test } from 'vitest';
2
3
 
3
4
  import { fileExists, folderExists } from '../src/config-utils';
4
5
 
@@ -1,76 +1,105 @@
1
+ import fs from 'fs';
1
2
  import os from 'os';
3
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2
4
 
3
5
  import { findConfigFile } from '../src/config-path';
4
6
 
5
- const mockmkDir = jest.fn();
6
- const mockaccessSync = jest.fn();
7
- const mockwriteFile = jest.fn();
8
-
9
- jest.mock('fs', () => {
10
- const fsOri = jest.requireActual('fs');
11
- return {
12
- ...fsOri,
13
- statSync: (path) => ({
14
- isDirectory: () => {
15
- if (path.match(/fail/)) {
16
- throw Error('file does not exist');
17
- }
18
- return true;
19
- },
20
- }),
21
- accessSync: (a) => mockaccessSync(a),
22
- mkdirSync: (a) => mockmkDir(a),
23
- writeFileSync: (a) => mockwriteFile(a),
7
+ describe('config-path', () => {
8
+ let statSyncMock;
9
+ let mkdirSyncMock;
10
+ let writeFileSyncMock;
11
+ let accessSyncMock;
12
+ let fakeStats = {
13
+ isDirectory: vi.fn().mockReturnValue(true),
24
14
  };
25
- });
26
-
27
- jest.mock('fs');
28
15
 
29
- describe('config-path', () => {
30
16
  beforeEach(() => {
31
- jest.clearAllMocks();
32
- jest.resetAllMocks();
17
+ // Mock only statSync method
18
+ statSyncMock = vi.spyOn(fs, 'statSync');
19
+ mkdirSyncMock = vi.spyOn(fs, 'mkdirSync');
20
+ writeFileSyncMock = vi.spyOn(fs, 'writeFileSync');
21
+ accessSyncMock = vi.spyOn(fs, 'accessSync');
22
+ });
23
+
24
+ afterEach(() => {
25
+ // Restore the original implementation after each test
26
+ statSyncMock.mockRestore();
27
+ vi.unstubAllEnvs();
33
28
  });
34
29
 
30
+ function platformPath(path: string): string {
31
+ return path.replace(/\//g, os.platform() === 'win32' ? '\\' : '/');
32
+ }
33
+
35
34
  describe('findConfigFile', () => {
36
- if (os.platform() !== 'win32') {
37
- describe('using defiled location from arguments', () => {
38
- test('with custom location', () => {
39
- expect(findConfigFile('/home/user/custom/location/config.yaml')).toEqual(
40
- '/home/user/custom/location/config.yaml'
41
- );
42
- expect(mockwriteFile).not.toHaveBeenCalled();
43
- expect(mockmkDir).not.toHaveBeenCalled();
44
- });
35
+ describe('using file location from arguments', () => {
36
+ test('with custom location', () => {
37
+ // mock
38
+ statSyncMock.mockReturnValue(fakeStats);
39
+ mkdirSyncMock.mockReturnValue(true);
40
+ writeFileSyncMock.mockReturnValue(undefined);
41
+ // Note: on Windows, path contains drive letter
42
+ expect(findConfigFile('/home/user/custom/location/config.yaml')).toMatch(
43
+ platformPath('/home/user/custom/location/config.yaml')
44
+ );
45
+ expect(writeFileSyncMock).not.toHaveBeenCalled();
46
+ expect(mkdirSyncMock).not.toHaveBeenCalled();
45
47
  });
48
+ });
46
49
 
47
- describe('whith env variables', () => {
48
- test('with XDG_CONFIG_HOME if directory exist but config file is missing', () => {
49
- process.env.XDG_CONFIG_HOME = '/home/user';
50
- expect(findConfigFile()).toEqual('/home/user/verdaccio/config.yaml');
51
- expect(mockwriteFile).toHaveBeenCalledWith('/home/user/verdaccio/config.yaml');
52
- expect(mockmkDir).toHaveBeenCalledWith('/home/user/verdaccio');
53
- });
50
+ describe('with env variables', () => {
51
+ test('the env XDG_CONFIG_HOME is defined and the directory exist but config file is missing', async () => {
52
+ // mock
53
+ statSyncMock.mockReturnValue(fakeStats);
54
+ mkdirSyncMock.mockReturnValue(true);
55
+ writeFileSyncMock.mockReturnValue(undefined);
56
+ // node env variable
57
+ vi.stubEnv('XDG_CONFIG_HOME', '/home/user');
54
58
 
55
- test('with HOME if directory exist but config file is missing', () => {
56
- delete process.env.XDG_CONFIG_HOME;
57
- process.env.HOME = '/home/user';
58
- expect(findConfigFile()).toEqual('/home/user/.config/verdaccio/config.yaml');
59
- expect(mockwriteFile).toHaveBeenCalledWith('/home/user/.config/verdaccio/config.yaml');
60
- expect(mockmkDir).toHaveBeenCalledWith('/home/user/.config/verdaccio');
61
- });
59
+ expect(findConfigFile()).toEqual(platformPath('/home/user/verdaccio/config.yaml'));
60
+ expect(writeFileSyncMock).toHaveBeenCalledWith(
61
+ platformPath('/home/user/verdaccio/config.yaml'),
62
+ expect.stringContaining('packages')
63
+ );
64
+ });
65
+
66
+ test('with HOME if directory exist but config file is missing', () => {
67
+ // mock
68
+ statSyncMock.mockReturnValue(fakeStats);
69
+ mkdirSyncMock.mockReturnValue(true);
70
+ writeFileSyncMock.mockReturnValue(undefined);
71
+ vi.stubEnv('XDG_CONFIG_HOME', '');
72
+ vi.stubEnv('HOME', '/home/user');
73
+ expect(findConfigFile()).toEqual(platformPath('/home/user/.config/verdaccio/config.yaml'));
74
+ expect(writeFileSyncMock).toHaveBeenCalledWith(
75
+ platformPath('/home/user/.config/verdaccio/config.yaml'),
76
+ expect.stringContaining('packages')
77
+ );
78
+ expect(mkdirSyncMock).toHaveBeenCalledWith(
79
+ platformPath('/home/user/.config/verdaccio'),
80
+ expect.anything()
81
+ );
82
+ });
62
83
 
63
- describe('error handling', () => {
64
- test('XDG_CONFIG_HOME is not directory fallback to default', () => {
65
- process.env.XDG_CONFIG_HOME = '/home/user/fail';
66
- mockaccessSync.mockImplementation(() => {});
67
- mockwriteFile.mockImplementation(() => {});
68
- expect(findConfigFile()).toMatch('packages/config/verdaccio/config.yaml');
84
+ describe('error handling', () => {
85
+ test('XDG_CONFIG_HOME is not directory fallback to default', () => {
86
+ // mock
87
+ statSyncMock.mockReturnValue({
88
+ isDirectory: vi.fn().mockReturnValue(false),
69
89
  });
90
+ mkdirSyncMock.mockReturnValue(true);
91
+ writeFileSyncMock.mockReturnValue(undefined);
92
+ // node env variable
93
+ vi.stubEnv('XDG_CONFIG_HOME', '/home/user/fail');
70
94
 
95
+ expect(findConfigFile()).toMatch(platformPath('packages/config/verdaccio/config.yaml'));
96
+ });
97
+
98
+ // Does not work on Windows
99
+ if (os.platform() !== 'win32') {
71
100
  test('no permissions on read default config file', () => {
72
- process.env.XDG_CONFIG_HOME = '/home/user';
73
- mockaccessSync.mockImplementation(() => {
101
+ vi.stubEnv('XDG_CONFIG_HOME', '/home/user');
102
+ accessSyncMock.mockImplementation(() => {
74
103
  throw new Error('error on write file');
75
104
  });
76
105
 
@@ -78,29 +107,28 @@ describe('config-path', () => {
78
107
  findConfigFile();
79
108
  }).toThrow(/configuration file does not have enough permissions for reading/);
80
109
  });
81
- });
110
+ }
82
111
  });
112
+ });
83
113
 
84
- describe('with no env variables', () => {
114
+ // Note: Trying to mock Windows platform leads to different results (incorrect slashes)
115
+ if (os.platform() === 'win32') {
116
+ describe('with Windows env variables', () => {
85
117
  test('with relative location', () => {
86
- mockaccessSync.mockImplementation(() => {});
87
- delete process.env.XDG_CONFIG_HOME;
88
- delete process.env.HOME;
89
- process.env.APPDATA = '/app/data/';
90
- expect(findConfigFile()).toMatch('packages/config/verdaccio/config.yaml');
91
- expect(mockwriteFile).toHaveBeenCalled();
92
- expect(mockmkDir).toHaveBeenCalled();
118
+ // mock
119
+ statSyncMock.mockReturnValue(fakeStats);
120
+ mkdirSyncMock.mockReturnValue(true);
121
+ writeFileSyncMock.mockReturnValue(undefined);
122
+ accessSyncMock.mockImplementation(() => {});
123
+ // delete process.env.XDG_CONFIG_HOME;
124
+ vi.stubEnv('XDG_CONFIG_HOME', '');
125
+ vi.stubEnv('HOME', '');
126
+ vi.stubEnv('APPDATA', 'C:\\Users\\Tester\\AppData\\');
127
+ expect(findConfigFile()).toEqual('C:\\Users\\Tester\\AppData\\verdaccio\\config.yaml');
128
+ expect(writeFileSyncMock).toHaveBeenCalled();
129
+ expect(mkdirSyncMock).toHaveBeenCalled();
93
130
  });
94
131
  });
95
- } else {
96
- test('with windows as directory exist but config file is missing', () => {
97
- delete process.env.XDG_CONFIG_HOME;
98
- delete process.env.HOME;
99
- process.env.APPDATA = '/app/data/';
100
- expect(findConfigFile()).toMatch('\\app\\data\\verdaccio\\config.yaml');
101
- expect(mockwriteFile).toHaveBeenCalled();
102
- expect(mockmkDir).toHaveBeenCalled();
103
- });
104
132
  }
105
133
  });
106
134
  });
@@ -1,14 +1,18 @@
1
1
  import _ from 'lodash';
2
2
  import path from 'path';
3
+ import { describe, expect, test } from 'vitest';
3
4
 
4
5
  import {
5
6
  Config,
6
7
  DEFAULT_REGISTRY,
7
8
  DEFAULT_UPLINK,
8
9
  ROLES,
10
+ TOKEN_VALID_LENGTH,
9
11
  WEB_TITLE,
10
12
  defaultSecurity,
13
+ generateRandomSecretKey,
11
14
  getDefaultConfig,
15
+ isNodeVersionGreaterThan21,
12
16
  parseConfigFile,
13
17
  } from '../src';
14
18
  import { parseConfigurationFile } from './utils';
@@ -19,6 +23,8 @@ const resolveConf = (conf) => {
19
23
  return path.join(__dirname, `../src/conf/${name}${ext.startsWith('.') ? ext : '.yaml'}`);
20
24
  };
21
25
 
26
+ const itif = (condition) => (condition ? test : test.skip);
27
+
22
28
  const checkDefaultUplink = (config) => {
23
29
  expect(_.isObject(config.uplinks[DEFAULT_UPLINK])).toBeTruthy();
24
30
  expect(config.uplinks[DEFAULT_UPLINK].url).toMatch(DEFAULT_REGISTRY);
@@ -94,32 +100,85 @@ describe('check basic content parsed file', () => {
94
100
  describe('checkSecretKey', () => {
95
101
  test('with default.yaml and pre selected secret', () => {
96
102
  const config = new Config(parseConfigFile(resolveConf('default')));
97
- expect(config.checkSecretKey('12345')).toEqual('12345');
103
+ expect(config.checkSecretKey(generateRandomSecretKey())).toHaveLength(TOKEN_VALID_LENGTH);
98
104
  });
99
105
 
100
106
  test('with default.yaml and void secret', () => {
101
107
  const config = new Config(parseConfigFile(resolveConf('default')));
102
- expect(typeof config.checkSecretKey() === 'string').toBeTruthy();
108
+ const secret = config.checkSecretKey();
109
+ expect(typeof secret === 'string').toBeTruthy();
110
+ expect(secret).toHaveLength(TOKEN_VALID_LENGTH);
103
111
  });
104
112
 
105
- test('with default.yaml and emtpy string secret', () => {
113
+ test('with default.yaml and empty string secret', () => {
106
114
  const config = new Config(parseConfigFile(resolveConf('default')));
107
- expect(typeof config.checkSecretKey('') === 'string').toBeTruthy();
115
+ const secret = config.checkSecretKey('');
116
+ expect(typeof secret === 'string').toBeTruthy();
117
+ expect(secret).toHaveLength(TOKEN_VALID_LENGTH);
108
118
  });
109
119
 
110
- test('with enhanced legacy signature', () => {
120
+ test('with default.yaml and valid string secret length', () => {
111
121
  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);
122
+ expect(typeof config.checkSecretKey(generateRandomSecretKey()) === 'string').toBeTruthy();
123
+ });
124
+
125
+ test('with default.yaml migrate a valid string secret length', () => {
126
+ const config = new Config(parseConfigFile(resolveConf('default')), {
127
+ forceMigrateToSecureLegacySignature: true,
128
+ });
129
+ expect(
130
+ // 64 characters secret long
131
+ config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
132
+ ).toHaveLength(TOKEN_VALID_LENGTH);
115
133
  });
116
134
 
117
- test('without enhanced legacy signature', () => {
135
+ // only runs on Node.js 22 or higher
136
+ itif(isNodeVersionGreaterThan21())('with enhanced legacy signature Node 22 or higher', () => {
137
+ const config = new Config(parseConfigFile(resolveConf('default')), {
138
+ forceMigrateToSecureLegacySignature: false,
139
+ });
140
+ // eslint-disable-next-line jest/no-standalone-expect
141
+ expect(() =>
142
+ // 64 characters secret long
143
+ config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
144
+ ).toThrow();
145
+ });
146
+
147
+ itif(isNodeVersionGreaterThan21())('with enhanced legacy signature Node 22 or higher', () => {
148
+ const config = new Config(parseConfigFile(resolveConf('default')), {
149
+ forceMigrateToSecureLegacySignature: false,
150
+ });
151
+ config.security.api.migrateToSecureLegacySignature = true;
152
+ // eslint-disable-next-line jest/no-standalone-expect
153
+ expect(
154
+ config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
155
+ ).toHaveLength(TOKEN_VALID_LENGTH);
156
+ });
157
+
158
+ itif(isNodeVersionGreaterThan21() === false)(
159
+ 'with old unsecure legacy signature Node 21 or lower',
160
+ () => {
161
+ const config = new Config(parseConfigFile(resolveConf('default')));
162
+ config.security.api.migrateToSecureLegacySignature = false;
163
+ // 64 characters secret long
164
+ // eslint-disable-next-line jest/no-standalone-expect
165
+ expect(
166
+ config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
167
+ ).toHaveLength(64);
168
+ }
169
+ );
170
+
171
+ test('with migration to new legacy signature Node 21 or lower', () => {
118
172
  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);
173
+ config.security.api.migrateToSecureLegacySignature = true;
174
+ // 64 characters secret long
175
+ // eslint-disable-next-line jest/no-standalone-expect
176
+ expect(
177
+ config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
178
+ ).toHaveLength(TOKEN_VALID_LENGTH);
122
179
  });
180
+
181
+ test.todo('test emit warning with secret key');
123
182
  });
124
183
 
125
184
  describe('getMatchedPackagesSpec', () => {
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash';
2
+ import { describe, expect, test } from 'vitest';
2
3
 
3
4
  import { parseConfigFile } from '../src';
4
5
  import { PACKAGE_ACCESS, normalisePackageAccess, normalizeUserList } from '../src/package-access';
@@ -1,3 +1,5 @@
1
+ import { expect, test } from 'vitest';
2
+
1
3
  import { TOKEN_VALID_LENGTH, generateRandomSecretKey } from '../src/token';
2
4
 
3
5
  test('token test valid length', () => {
@@ -1,3 +1,5 @@
1
+ import { describe, expect, test } from 'vitest';
2
+
1
3
  import { normalisePackageAccess, parseConfigFile } from '../src';
2
4
  import { hasProxyTo, sanityCheckUplinksProps, uplinkSanityCheck } from '../src/uplinks';
3
5
  import { parseConfigurationFile } from './utils';
@@ -1,3 +1,5 @@
1
+ import { describe, expect, test } from 'vitest';
2
+
1
3
  import { ROLES, createAnonymousRemoteUser, createRemoteUser } from '../src';
2
4
 
3
5
  describe('createRemoteUser and createAnonymousRemoteUser', () => {
package/jest.config.js DELETED
@@ -1,9 +0,0 @@
1
- const config = require('../../jest/config');
2
-
3
- module.exports = Object.assign({}, config, {
4
- coverageThreshold: {
5
- global: {
6
- lines: 90,
7
- },
8
- },
9
- });