@redocly/cli 1.7.0 → 1.8.0

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 (118) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/lib/__tests__/commands/build-docs.test.js +3 -3
  3. package/lib/__tests__/commands/bundle.test.js +5 -5
  4. package/lib/__tests__/commands/join.test.js +11 -11
  5. package/lib/__tests__/commands/lint.test.js +14 -14
  6. package/lib/__tests__/commands/push-region.test.js +1 -1
  7. package/lib/__tests__/commands/push.test.js +11 -11
  8. package/lib/__tests__/fetch-with-timeout.test.js +4 -13
  9. package/lib/__tests__/spinner.test.js +43 -0
  10. package/lib/__tests__/utils.test.js +36 -36
  11. package/lib/__tests__/wrapper.test.js +8 -8
  12. package/lib/cms/api/__tests__/api-keys.test.d.ts +1 -0
  13. package/lib/cms/api/__tests__/api-keys.test.js +26 -0
  14. package/lib/cms/api/__tests__/api.client.test.d.ts +1 -0
  15. package/lib/cms/api/__tests__/api.client.test.js +217 -0
  16. package/lib/cms/api/__tests__/domains.test.d.ts +1 -0
  17. package/lib/cms/api/__tests__/domains.test.js +13 -0
  18. package/lib/cms/api/api-client.d.ts +50 -0
  19. package/lib/cms/api/api-client.js +148 -0
  20. package/lib/cms/api/api-keys.d.ts +1 -0
  21. package/lib/cms/api/api-keys.js +24 -0
  22. package/lib/cms/api/domains.d.ts +1 -0
  23. package/lib/cms/api/domains.js +12 -0
  24. package/lib/cms/api/index.d.ts +3 -0
  25. package/lib/cms/api/index.js +19 -0
  26. package/lib/cms/api/types.d.ts +91 -0
  27. package/lib/cms/api/types.js +2 -0
  28. package/lib/cms/commands/__tests__/push-status.test.d.ts +1 -0
  29. package/lib/cms/commands/__tests__/push-status.test.js +164 -0
  30. package/lib/cms/commands/__tests__/push.test.d.ts +1 -0
  31. package/lib/cms/commands/__tests__/push.test.js +226 -0
  32. package/lib/cms/commands/push-status.d.ts +12 -0
  33. package/lib/cms/commands/push-status.js +150 -0
  34. package/lib/cms/commands/push.d.ts +23 -0
  35. package/lib/cms/commands/push.js +142 -0
  36. package/lib/cms/utils.d.ts +2 -0
  37. package/lib/cms/utils.js +6 -0
  38. package/lib/commands/build-docs/index.js +4 -4
  39. package/lib/commands/build-docs/utils.js +2 -2
  40. package/lib/commands/bundle.js +13 -13
  41. package/lib/commands/join.js +25 -25
  42. package/lib/commands/lint.js +10 -10
  43. package/lib/commands/login.js +2 -2
  44. package/lib/commands/preview-docs/index.js +4 -4
  45. package/lib/commands/preview-docs/preview-server/preview-server.js +2 -2
  46. package/lib/commands/preview-project/types.d.ts +1 -1
  47. package/lib/commands/push.d.ts +5 -0
  48. package/lib/commands/push.js +25 -17
  49. package/lib/commands/split/__tests__/index.test.js +2 -2
  50. package/lib/commands/split/index.js +17 -17
  51. package/lib/commands/stats.js +4 -4
  52. package/lib/index.d.ts +1 -1
  53. package/lib/index.js +130 -17
  54. package/lib/types.d.ts +8 -1
  55. package/lib/{__mocks__/utils.js → utils/__mocks__/miscellaneous.js} +1 -1
  56. package/lib/utils/assert-node-version.d.ts +1 -0
  57. package/lib/{fetch-with-timeout.js → utils/fetch-with-timeout.js} +2 -7
  58. package/lib/{utils.d.ts → utils/miscellaneous.d.ts} +1 -1
  59. package/lib/{utils.js → utils/miscellaneous.js} +2 -2
  60. package/lib/utils/spinner.d.ts +10 -0
  61. package/lib/utils/spinner.js +42 -0
  62. package/lib/{update-version-notifier.js → utils/update-version-notifier.js} +4 -4
  63. package/lib/wrapper.js +5 -5
  64. package/package.json +5 -3
  65. package/src/__tests__/commands/build-docs.test.ts +2 -2
  66. package/src/__tests__/commands/bundle.test.ts +2 -2
  67. package/src/__tests__/commands/join.test.ts +2 -2
  68. package/src/__tests__/commands/lint.test.ts +3 -3
  69. package/src/__tests__/commands/push-region.test.ts +1 -1
  70. package/src/__tests__/commands/push.test.ts +2 -2
  71. package/src/__tests__/fetch-with-timeout.test.ts +4 -16
  72. package/src/__tests__/spinner.test.ts +51 -0
  73. package/src/__tests__/utils.test.ts +2 -5
  74. package/src/__tests__/wrapper.test.ts +2 -2
  75. package/src/cms/api/__tests__/api-keys.test.ts +37 -0
  76. package/src/cms/api/__tests__/api.client.test.ts +275 -0
  77. package/src/cms/api/__tests__/domains.test.ts +15 -0
  78. package/src/cms/api/api-client.ts +199 -0
  79. package/src/cms/api/api-keys.ts +26 -0
  80. package/src/cms/api/domains.ts +11 -0
  81. package/src/cms/api/index.ts +3 -0
  82. package/src/cms/api/types.ts +101 -0
  83. package/src/cms/commands/__tests__/push-status.test.ts +212 -0
  84. package/src/cms/commands/__tests__/push.test.ts +293 -0
  85. package/src/cms/commands/push-status.ts +203 -0
  86. package/src/cms/commands/push.ts +215 -0
  87. package/src/cms/utils.ts +1 -0
  88. package/src/commands/build-docs/index.ts +1 -1
  89. package/src/commands/build-docs/utils.ts +1 -1
  90. package/src/commands/bundle.ts +2 -2
  91. package/src/commands/join.ts +2 -2
  92. package/src/commands/lint.ts +1 -1
  93. package/src/commands/login.ts +1 -1
  94. package/src/commands/preview-docs/index.ts +5 -1
  95. package/src/commands/preview-docs/preview-server/preview-server.ts +1 -1
  96. package/src/commands/preview-project/types.ts +1 -1
  97. package/src/commands/push.ts +15 -1
  98. package/src/commands/split/__tests__/index.test.ts +3 -4
  99. package/src/commands/split/index.ts +2 -2
  100. package/src/commands/stats.ts +2 -2
  101. package/src/index.ts +138 -20
  102. package/src/types.ts +8 -0
  103. package/src/{__mocks__/utils.ts → utils/__mocks__/miscellaneous.ts} +1 -1
  104. package/src/{fetch-with-timeout.ts → utils/fetch-with-timeout.ts} +1 -6
  105. package/src/{utils.ts → utils/miscellaneous.ts} +2 -2
  106. package/src/utils/spinner.ts +50 -0
  107. package/src/{update-version-notifier.ts → utils/update-version-notifier.ts} +2 -2
  108. package/src/wrapper.ts +7 -2
  109. package/tsconfig.tsbuildinfo +1 -1
  110. /package/lib/{assert-node-version.d.ts → __tests__/spinner.test.d.ts} +0 -0
  111. /package/lib/{__mocks__/utils.d.ts → utils/__mocks__/miscellaneous.d.ts} +0 -0
  112. /package/lib/{assert-node-version.js → utils/assert-node-version.js} +0 -0
  113. /package/lib/{fetch-with-timeout.d.ts → utils/fetch-with-timeout.d.ts} +0 -0
  114. /package/lib/{js-utils.d.ts → utils/js-utils.d.ts} +0 -0
  115. /package/lib/{js-utils.js → utils/js-utils.js} +0 -0
  116. /package/lib/{update-version-notifier.d.ts → utils/update-version-notifier.d.ts} +0 -0
  117. /package/src/{assert-node-version.ts → utils/assert-node-version.ts} +0 -0
  118. /package/src/{js-utils.ts → utils/js-utils.ts} +0 -0
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const utils_1 = require("../utils");
12
+ const miscellaneous_1 = require("../utils/miscellaneous");
13
13
  const openapi_core_1 = require("@redocly/openapi-core");
14
14
  const colorette_1 = require("colorette");
15
15
  const fs_1 = require("fs");
@@ -31,7 +31,7 @@ describe('isSubdir', () => {
31
31
  ['/foo', './bar', false],
32
32
  ['/foo', '/foo/..bar', true],
33
33
  ].forEach(([parent, child, expectRes]) => {
34
- expect((0, utils_1.isSubdir)(parent, child)).toBe(expectRes);
34
+ expect((0, miscellaneous_1.isSubdir)(parent, child)).toBe(expectRes);
35
35
  });
36
36
  });
37
37
  it('can correctly determine if subdir for windows-based paths', () => {
@@ -42,7 +42,7 @@ describe('isSubdir', () => {
42
42
  ['C:\\Foo', 'C:\\Bar', false],
43
43
  ['C:\\Foo', 'D:\\Foo\\Bar', false],
44
44
  ].forEach(([parent, child, expectRes]) => {
45
- expect((0, utils_1.isSubdir)(parent, child)).toBe(expectRes);
45
+ expect((0, miscellaneous_1.isSubdir)(parent, child)).toBe(expectRes);
46
46
  });
47
47
  });
48
48
  afterEach(() => {
@@ -51,14 +51,14 @@ describe('isSubdir', () => {
51
51
  });
52
52
  describe('pathToFilename', () => {
53
53
  it('should use correct path separator', () => {
54
- const processedPath = (0, utils_1.pathToFilename)('/user/createWithList', '_');
54
+ const processedPath = (0, miscellaneous_1.pathToFilename)('/user/createWithList', '_');
55
55
  expect(processedPath).toEqual('user_createWithList');
56
56
  });
57
57
  });
58
58
  describe('getFallbackApisOrExit', () => {
59
59
  it('should find alias by filename', () => __awaiter(void 0, void 0, void 0, function* () {
60
60
  fs_1.existsSync.mockImplementationOnce(() => true);
61
- const entry = yield (0, utils_1.getFallbackApisOrExit)(['./test.yaml'], {
61
+ const entry = yield (0, miscellaneous_1.getFallbackApisOrExit)(['./test.yaml'], {
62
62
  apis: {
63
63
  main: {
64
64
  root: 'test.yaml',
@@ -82,17 +82,17 @@ describe('printConfigLintTotals', () => {
82
82
  jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
83
83
  });
84
84
  it('should print errors if such exist', () => {
85
- (0, utils_1.printConfigLintTotals)(totalProblemsMock);
85
+ (0, miscellaneous_1.printConfigLintTotals)(totalProblemsMock);
86
86
  expect(process.stderr.write).toHaveBeenCalledWith('❌ Your config has 1 error.');
87
87
  expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error.');
88
88
  });
89
89
  it('should print warnign if no error', () => {
90
- (0, utils_1.printConfigLintTotals)(Object.assign(Object.assign({}, totalProblemsMock), { errors: 0, warnings: 2 }));
90
+ (0, miscellaneous_1.printConfigLintTotals)(Object.assign(Object.assign({}, totalProblemsMock), { errors: 0, warnings: 2 }));
91
91
  expect(process.stderr.write).toHaveBeenCalledWith('⚠️ Your config has 2 warnings.\n');
92
92
  expect(yellowColoretteMocks).toHaveBeenCalledWith('⚠️ Your config has 2 warnings.\n');
93
93
  });
94
94
  it('should print nothing if no error and no warnings', () => {
95
- const result = (0, utils_1.printConfigLintTotals)(Object.assign(Object.assign({}, totalProblemsMock), { errors: 0 }));
95
+ const result = (0, miscellaneous_1.printConfigLintTotals)(Object.assign(Object.assign({}, totalProblemsMock), { errors: 0 }));
96
96
  expect(result).toBeUndefined();
97
97
  expect(process.stderr.write).toHaveBeenCalledTimes(0);
98
98
  expect(yellowColoretteMocks).toHaveBeenCalledTimes(0);
@@ -124,7 +124,7 @@ describe('getFallbackApisOrExit', () => {
124
124
  };
125
125
  expect.assertions(1);
126
126
  try {
127
- yield (0, utils_1.getFallbackApisOrExit)([''], apisConfig);
127
+ yield (0, miscellaneous_1.getFallbackApisOrExit)([''], apisConfig);
128
128
  }
129
129
  catch (e) {
130
130
  expect(e.message).toEqual('Path cannot be empty.');
@@ -134,7 +134,7 @@ describe('getFallbackApisOrExit', () => {
134
134
  fs_1.existsSync.mockImplementationOnce(() => false);
135
135
  expect.assertions(3);
136
136
  try {
137
- yield (0, utils_1.getFallbackApisOrExit)(undefined, config);
137
+ yield (0, miscellaneous_1.getFallbackApisOrExit)(undefined, config);
138
138
  }
139
139
  catch (e) {
140
140
  expect(process.stderr.write).toHaveBeenCalledWith('\nsomeFile.yaml does not exist or is invalid.\n\n');
@@ -145,7 +145,7 @@ describe('getFallbackApisOrExit', () => {
145
145
  it('should return valid array with results if such file exist', () => __awaiter(void 0, void 0, void 0, function* () {
146
146
  fs_1.existsSync.mockImplementationOnce(() => true);
147
147
  jest.spyOn(path, 'resolve').mockImplementationOnce((_, path) => path);
148
- const result = yield (0, utils_1.getFallbackApisOrExit)(undefined, config);
148
+ const result = yield (0, miscellaneous_1.getFallbackApisOrExit)(undefined, config);
149
149
  expect(process.stderr.write).toHaveBeenCalledTimes(0);
150
150
  expect(process.exit).toHaveBeenCalledTimes(0);
151
151
  expect(result).toStrictEqual([
@@ -162,7 +162,7 @@ describe('getFallbackApisOrExit', () => {
162
162
  fs_1.existsSync.mockImplementationOnce(() => false);
163
163
  expect.assertions(3);
164
164
  try {
165
- yield (0, utils_1.getFallbackApisOrExit)(['someFile.yaml'], apisConfig);
165
+ yield (0, miscellaneous_1.getFallbackApisOrExit)(['someFile.yaml'], apisConfig);
166
166
  }
167
167
  catch (e) {
168
168
  expect(process.stderr.write).toHaveBeenCalledWith('\nsomeFile.yaml does not exist or is invalid.\n\n');
@@ -177,7 +177,7 @@ describe('getFallbackApisOrExit', () => {
177
177
  fs_1.existsSync.mockImplementationOnce(() => false);
178
178
  expect.assertions(3);
179
179
  try {
180
- yield (0, utils_1.getFallbackApisOrExit)(['someFile.yaml', 'someFile2.yaml'], apisConfig);
180
+ yield (0, miscellaneous_1.getFallbackApisOrExit)(['someFile.yaml', 'someFile2.yaml'], apisConfig);
181
181
  }
182
182
  catch (e) {
183
183
  expect(process.stderr.write).toHaveBeenCalledWith('\nsomeFile.yaml does not exist or is invalid.\n\n');
@@ -194,7 +194,7 @@ describe('getFallbackApisOrExit', () => {
194
194
  const existSyncMock = fs_1.existsSync.mockImplementation((path) => path.endsWith('someFile.yaml'));
195
195
  expect.assertions(4);
196
196
  try {
197
- yield (0, utils_1.getFallbackApisOrExit)(undefined, configStub);
197
+ yield (0, miscellaneous_1.getFallbackApisOrExit)(undefined, configStub);
198
198
  }
199
199
  catch (e) {
200
200
  expect(process.stderr.write).toHaveBeenCalledWith('\nnotExist.yaml does not exist or is invalid.\n\n');
@@ -215,7 +215,7 @@ describe('getFallbackApisOrExit', () => {
215
215
  },
216
216
  },
217
217
  };
218
- const result = yield (0, utils_1.getFallbackApisOrExit)(undefined, apisConfig);
218
+ const result = yield (0, miscellaneous_1.getFallbackApisOrExit)(undefined, apisConfig);
219
219
  expect(process.stderr.write).toHaveBeenCalledTimes(0);
220
220
  expect(result).toStrictEqual([
221
221
  {
@@ -254,10 +254,10 @@ describe('langToExt', () => {
254
254
  ['typescript', '.ts'],
255
255
  ['tsx', '.tsx'],
256
256
  ])('should infer file extension from lang - %s', (lang, expected) => {
257
- expect((0, utils_1.langToExt)(lang)).toBe(expected);
257
+ expect((0, miscellaneous_1.langToExt)(lang)).toBe(expected);
258
258
  });
259
259
  it('should ignore case when inferring file extension', () => {
260
- expect((0, utils_1.langToExt)('JavaScript')).toBe('.js');
260
+ expect((0, miscellaneous_1.langToExt)('JavaScript')).toBe('.js');
261
261
  });
262
262
  });
263
263
  describe('sorTopLevelKeysForOas', () => {
@@ -288,7 +288,7 @@ describe('sorTopLevelKeysForOas', () => {
288
288
  'x-webhooks',
289
289
  'components',
290
290
  ];
291
- const result = (0, utils_1.sortTopLevelKeysForOas)(openApi);
291
+ const result = (0, miscellaneous_1.sortTopLevelKeysForOas)(openApi);
292
292
  Object.keys(result).forEach((key, index) => {
293
293
  expect(key).toEqual(orderedKeys[index]);
294
294
  });
@@ -328,7 +328,7 @@ describe('sorTopLevelKeysForOas', () => {
328
328
  'responses',
329
329
  'securityDefinitions',
330
330
  ];
331
- const result = (0, utils_1.sortTopLevelKeysForOas)(openApi);
331
+ const result = (0, miscellaneous_1.sortTopLevelKeysForOas)(openApi);
332
332
  Object.keys(result).forEach((key, index) => {
333
333
  expect(key).toEqual(orderedKeys[index]);
334
334
  });
@@ -349,31 +349,31 @@ describe('handleErrors', () => {
349
349
  });
350
350
  it('should handle ResolveError', () => {
351
351
  const resolveError = new openapi_core_1.ResolveError(new Error('File not found'));
352
- expect(() => (0, utils_1.handleError)(resolveError, ref)).toThrowError(utils_1.HandledError);
352
+ expect(() => (0, miscellaneous_1.handleError)(resolveError, ref)).toThrowError(miscellaneous_1.HandledError);
353
353
  expect(redColoretteMocks).toHaveBeenCalledTimes(1);
354
354
  expect(process.stderr.write).toHaveBeenCalledWith(`Failed to resolve API description at openapi/test.yaml:\n\n - File not found.\n\n`);
355
355
  });
356
356
  it('should handle YamlParseError', () => {
357
357
  const yamlParseError = new openapi_core_1.YamlParseError(new Error('Invalid yaml'), {});
358
- expect(() => (0, utils_1.handleError)(yamlParseError, ref)).toThrowError(utils_1.HandledError);
358
+ expect(() => (0, miscellaneous_1.handleError)(yamlParseError, ref)).toThrowError(miscellaneous_1.HandledError);
359
359
  expect(redColoretteMocks).toHaveBeenCalledTimes(1);
360
360
  expect(process.stderr.write).toHaveBeenCalledWith(`Failed to parse API description at openapi/test.yaml:\n\n - Invalid yaml.\n\n`);
361
361
  });
362
362
  it('should handle CircularJSONNotSupportedError', () => {
363
- const circularError = new utils_1.CircularJSONNotSupportedError(new Error('Circular json'));
364
- expect(() => (0, utils_1.handleError)(circularError, ref)).toThrowError(utils_1.HandledError);
363
+ const circularError = new miscellaneous_1.CircularJSONNotSupportedError(new Error('Circular json'));
364
+ expect(() => (0, miscellaneous_1.handleError)(circularError, ref)).toThrowError(miscellaneous_1.HandledError);
365
365
  expect(process.stderr.write).toHaveBeenCalledWith(`Detected circular reference which can't be converted to JSON.\n` +
366
366
  `Try to use ${(0, colorette_1.blue)('yaml')} output or remove ${(0, colorette_1.blue)('--dereferenced')}.\n\n`);
367
367
  });
368
368
  it('should handle SyntaxError', () => {
369
369
  const testError = new SyntaxError('Unexpected identifier');
370
370
  testError.stack = 'test stack';
371
- expect(() => (0, utils_1.handleError)(testError, ref)).toThrowError(utils_1.HandledError);
371
+ expect(() => (0, miscellaneous_1.handleError)(testError, ref)).toThrowError(miscellaneous_1.HandledError);
372
372
  expect(process.stderr.write).toHaveBeenCalledWith('Syntax error: Unexpected identifier test stack\n\n');
373
373
  });
374
374
  it('should throw unknown error', () => {
375
375
  const testError = new Error('Test error');
376
- expect(() => (0, utils_1.handleError)(testError, ref)).toThrowError(utils_1.HandledError);
376
+ expect(() => (0, miscellaneous_1.handleError)(testError, ref)).toThrowError(miscellaneous_1.HandledError);
377
377
  expect(process.stderr.write).toHaveBeenCalledWith(`Something went wrong when processing openapi/test.yaml:\n\n - Test error.\n\n`);
378
378
  });
379
379
  });
@@ -391,7 +391,7 @@ describe('checkIfRulesetExist', () => {
391
391
  oas3_1: {},
392
392
  async2: {},
393
393
  };
394
- expect(() => (0, utils_1.checkIfRulesetExist)(rules)).toThrowError('⚠️ No rules were configured. Learn how to configure rules: https://redocly.com/docs/cli/rules/');
394
+ expect(() => (0, miscellaneous_1.checkIfRulesetExist)(rules)).toThrowError('⚠️ No rules were configured. Learn how to configure rules: https://redocly.com/docs/cli/rules/');
395
395
  });
396
396
  it('should not throw an error if rules are provided', () => {
397
397
  const rules = {
@@ -399,13 +399,13 @@ describe('checkIfRulesetExist', () => {
399
399
  oas3_0: {},
400
400
  oas3_1: {},
401
401
  };
402
- (0, utils_1.checkIfRulesetExist)(rules);
402
+ (0, miscellaneous_1.checkIfRulesetExist)(rules);
403
403
  });
404
404
  });
405
405
  describe('cleanColors', () => {
406
406
  it('should remove colors from string', () => {
407
407
  const stringWithColors = `String for ${(0, colorette_1.red)('test')}`;
408
- const result = (0, utils_1.cleanColors)(stringWithColors);
408
+ const result = (0, miscellaneous_1.cleanColors)(stringWithColors);
409
409
  expect(result).not.toMatch(/\x1b\[\d+m/g);
410
410
  });
411
411
  });
@@ -427,7 +427,7 @@ describe('cleanArgs', () => {
427
427
  apis: ['main@v1', 'fixtures/openapi.yaml', 'http://some.url/openapi.yaml'],
428
428
  format: 'codeframe',
429
429
  };
430
- expect((0, utils_1.cleanArgs)(testArgs)).toEqual({
430
+ expect((0, miscellaneous_1.cleanArgs)(testArgs)).toEqual({
431
431
  config: 'file-yaml',
432
432
  apis: ['api-name@api-version', 'file-yaml', 'http://url'],
433
433
  format: 'codeframe',
@@ -437,7 +437,7 @@ describe('cleanArgs', () => {
437
437
  const testArgs = {
438
438
  destination: '@org/name@version',
439
439
  };
440
- expect((0, utils_1.cleanArgs)(testArgs)).toEqual({
440
+ expect((0, miscellaneous_1.cleanArgs)(testArgs)).toEqual({
441
441
  destination: '@organization/api-name@api-version',
442
442
  });
443
443
  });
@@ -465,7 +465,7 @@ describe('cleanRawInput', () => {
465
465
  '--output',
466
466
  'fixtures',
467
467
  ];
468
- expect((0, utils_1.cleanRawInput)(rawInput)).toEqual('redocly bundle api-name@api-version file-yaml http://url --config=file-yaml --output folder');
468
+ expect((0, miscellaneous_1.cleanRawInput)(rawInput)).toEqual('redocly bundle api-name@api-version file-yaml http://url --config=file-yaml --output folder');
469
469
  });
470
470
  it('should preserve safe data from raw CLI input', () => {
471
471
  const rawInput = [
@@ -478,16 +478,16 @@ describe('cleanRawInput', () => {
478
478
  '--skip-rule',
479
479
  'operation-4xx-response',
480
480
  ];
481
- expect((0, utils_1.cleanRawInput)(rawInput)).toEqual('redocly lint file-json --format stylish --extends=minimal --skip-rule operation-4xx-response');
481
+ expect((0, miscellaneous_1.cleanRawInput)(rawInput)).toEqual('redocly lint file-json --format stylish --extends=minimal --skip-rule operation-4xx-response');
482
482
  });
483
483
  describe('validateFileExtension', () => {
484
484
  it('should return current file extension', () => {
485
- expect((0, utils_1.getAndValidateFileExtension)('test.json')).toEqual('json');
485
+ expect((0, miscellaneous_1.getAndValidateFileExtension)('test.json')).toEqual('json');
486
486
  });
487
487
  it('should return yaml and print warning if file extension does not supported', () => {
488
488
  const stderrMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
489
489
  colorette_1.yellow.mockImplementation((text) => text);
490
- expect((0, utils_1.getAndValidateFileExtension)('test.xml')).toEqual('yaml');
490
+ expect((0, miscellaneous_1.getAndValidateFileExtension)('test.xml')).toEqual('yaml');
491
491
  expect(stderrMock).toHaveBeenCalledWith(`Unsupported file extension: xml. Using yaml.\n`);
492
492
  });
493
493
  });
@@ -500,13 +500,13 @@ describe('cleanRawInput', () => {
500
500
  jest.restoreAllMocks();
501
501
  });
502
502
  it('should call stringifyYaml function', () => {
503
- (0, utils_1.writeToFileByExtension)('test data', 'test.yaml');
503
+ (0, miscellaneous_1.writeToFileByExtension)('test data', 'test.yaml');
504
504
  expect(openapi_core_1.stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
505
505
  expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
506
506
  });
507
507
  it('should call JSON.stringify function', () => {
508
508
  const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
509
- (0, utils_1.writeToFileByExtension)('test data', 'test.json');
509
+ (0, miscellaneous_1.writeToFileByExtension)('test data', 'test.json');
510
510
  expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
511
511
  expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
512
512
  });
@@ -9,13 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const utils_1 = require("../utils");
12
+ const miscellaneous_1 = require("../utils/miscellaneous");
13
13
  const process = require("process");
14
14
  const wrapper_1 = require("../wrapper");
15
15
  const lint_1 = require("../commands/lint");
16
16
  const push_1 = require("../commands/push");
17
17
  jest.mock('node-fetch');
18
- jest.mock('../utils', () => ({
18
+ jest.mock('../utils/miscellaneous', () => ({
19
19
  sendTelemetry: jest.fn(),
20
20
  loadConfigAndHandleErrors: jest.fn(),
21
21
  }));
@@ -25,29 +25,29 @@ jest.mock('../commands/lint', () => ({
25
25
  }));
26
26
  describe('commandWrapper', () => {
27
27
  it('should send telemetry if there is "telemetry: on" in the config', () => __awaiter(void 0, void 0, void 0, function* () {
28
- utils_1.loadConfigAndHandleErrors.mockImplementation(() => {
28
+ miscellaneous_1.loadConfigAndHandleErrors.mockImplementation(() => {
29
29
  return { telemetry: 'on', styleguide: { recommendedFallback: true } };
30
30
  });
31
31
  process.env.REDOCLY_TELEMETRY = 'on';
32
32
  const wrappedHandler = (0, wrapper_1.commandWrapper)(lint_1.handleLint);
33
33
  yield wrappedHandler({});
34
34
  expect(lint_1.handleLint).toHaveBeenCalledTimes(1);
35
- expect(utils_1.sendTelemetry).toHaveBeenCalledTimes(1);
36
- expect(utils_1.sendTelemetry).toHaveBeenCalledWith({}, 0, false);
35
+ expect(miscellaneous_1.sendTelemetry).toHaveBeenCalledTimes(1);
36
+ expect(miscellaneous_1.sendTelemetry).toHaveBeenCalledWith({}, 0, false);
37
37
  }));
38
38
  it('should NOT send telemetry if there is "telemetry: off" in the config', () => __awaiter(void 0, void 0, void 0, function* () {
39
- utils_1.loadConfigAndHandleErrors.mockImplementation(() => {
39
+ miscellaneous_1.loadConfigAndHandleErrors.mockImplementation(() => {
40
40
  return { telemetry: 'off', styleguide: { recommendedFallback: true } };
41
41
  });
42
42
  process.env.REDOCLY_TELEMETRY = 'on';
43
43
  const wrappedHandler = (0, wrapper_1.commandWrapper)(lint_1.handleLint);
44
44
  yield wrappedHandler({});
45
45
  expect(lint_1.handleLint).toHaveBeenCalledTimes(1);
46
- expect(utils_1.sendTelemetry).toHaveBeenCalledTimes(0);
46
+ expect(miscellaneous_1.sendTelemetry).toHaveBeenCalledTimes(0);
47
47
  }));
48
48
  it('should pass files from arguments to config', () => __awaiter(void 0, void 0, void 0, function* () {
49
49
  const filesToPush = ['test1.yaml', 'test2.yaml'];
50
- const loadConfigMock = utils_1.loadConfigAndHandleErrors;
50
+ const loadConfigMock = miscellaneous_1.loadConfigAndHandleErrors;
51
51
  const argv = {
52
52
  files: filesToPush,
53
53
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const api_keys_1 = require("../api-keys");
4
+ const fs = require("fs");
5
+ describe('getApiKeys()', () => {
6
+ afterEach(() => {
7
+ jest.resetAllMocks();
8
+ });
9
+ it('should return api key from environment variable', () => {
10
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
11
+ expect((0, api_keys_1.getApiKeys)('test-domain')).toEqual('test-api-key');
12
+ });
13
+ it('should return api key from credentials file', () => {
14
+ process.env.REDOCLY_AUTHORIZATION = '';
15
+ jest.spyOn(fs, 'existsSync').mockReturnValue(true);
16
+ jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
17
+ ['test-domain']: 'test-api-key-from-credentials-file',
18
+ }));
19
+ expect((0, api_keys_1.getApiKeys)('test-domain')).toEqual('test-api-key-from-credentials-file');
20
+ });
21
+ it('should throw an error if no api key provided', () => {
22
+ process.env.REDOCLY_AUTHORIZATION = '';
23
+ jest.spyOn(fs, 'existsSync').mockReturnValue(false);
24
+ expect(() => (0, api_keys_1.getApiKeys)('test-domain')).toThrowError('No api key provided, please use environment variable REDOCLY_AUTHORIZATION.');
25
+ });
26
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const node_fetch_1 = require("node-fetch");
13
+ const FormData = require("form-data");
14
+ const api_client_1 = require("../api-client");
15
+ jest.mock('node-fetch', () => ({
16
+ default: jest.fn(),
17
+ }));
18
+ function mockFetchResponse(response) {
19
+ node_fetch_1.default.mockResolvedValue(response);
20
+ }
21
+ describe('ApiClient', () => {
22
+ const testToken = 'test-token';
23
+ const testDomain = 'test-domain.com';
24
+ const testOrg = 'test-org';
25
+ const testProject = 'test-project';
26
+ describe('getDefaultBranch()', () => {
27
+ let apiClient;
28
+ beforeEach(() => {
29
+ apiClient = new api_client_1.ReuniteApiClient(testDomain, testToken);
30
+ });
31
+ it('should get default project branch', () => __awaiter(void 0, void 0, void 0, function* () {
32
+ mockFetchResponse({
33
+ ok: true,
34
+ json: jest.fn().mockResolvedValue({
35
+ branchName: 'test-branch',
36
+ }),
37
+ });
38
+ const result = yield apiClient.remotes.getDefaultBranch(testOrg, testProject);
39
+ expect(node_fetch_1.default).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/source`, {
40
+ method: 'GET',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ Authorization: `Bearer ${testToken}`,
44
+ },
45
+ });
46
+ expect(result).toEqual('test-branch');
47
+ }));
48
+ it('should throw parsed error if response is not ok', () => __awaiter(void 0, void 0, void 0, function* () {
49
+ mockFetchResponse({
50
+ ok: false,
51
+ json: jest.fn().mockResolvedValue({
52
+ type: 'about:blank',
53
+ title: 'Project source not found',
54
+ status: 404,
55
+ detail: 'Not Found',
56
+ object: 'problem',
57
+ }),
58
+ });
59
+ yield expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(new Error('Failed to fetch default branch: Project source not found'));
60
+ }));
61
+ it('should throw statusText error if response is not ok', () => __awaiter(void 0, void 0, void 0, function* () {
62
+ mockFetchResponse({
63
+ ok: false,
64
+ statusText: 'Not found',
65
+ json: jest.fn().mockResolvedValue({
66
+ unknownField: 'unknown-error',
67
+ }),
68
+ });
69
+ yield expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(new Error('Failed to fetch default branch: Not found'));
70
+ }));
71
+ });
72
+ describe('upsert()', () => {
73
+ const remotePayload = {
74
+ mountBranchName: 'remote-mount-branch-name',
75
+ mountPath: 'remote-mount-path',
76
+ };
77
+ let apiClient;
78
+ beforeEach(() => {
79
+ apiClient = new api_client_1.ReuniteApiClient(testDomain, testToken);
80
+ });
81
+ it('should upsert remote', () => __awaiter(void 0, void 0, void 0, function* () {
82
+ const responseMock = {
83
+ id: 'remote-id',
84
+ type: 'CICD',
85
+ mountPath: 'remote-mount-path',
86
+ mountBranchName: 'remote-mount-branch-name',
87
+ organizationId: testOrg,
88
+ projectId: testProject,
89
+ };
90
+ mockFetchResponse({
91
+ ok: true,
92
+ json: jest.fn().mockResolvedValue(responseMock),
93
+ });
94
+ const result = yield apiClient.remotes.upsert(testOrg, testProject, remotePayload);
95
+ expect(node_fetch_1.default).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/remotes`, {
96
+ method: 'POST',
97
+ headers: {
98
+ 'Content-Type': 'application/json',
99
+ Authorization: `Bearer ${testToken}`,
100
+ },
101
+ body: JSON.stringify({
102
+ mountPath: remotePayload.mountPath,
103
+ mountBranchName: remotePayload.mountBranchName,
104
+ type: 'CICD',
105
+ autoMerge: true,
106
+ }),
107
+ });
108
+ expect(result).toEqual(responseMock);
109
+ }));
110
+ it('should throw parsed error if response is not ok', () => __awaiter(void 0, void 0, void 0, function* () {
111
+ mockFetchResponse({
112
+ ok: false,
113
+ json: jest.fn().mockResolvedValue({
114
+ type: 'about:blank',
115
+ title: 'Not allowed to mount remote outside of project content path: /docs',
116
+ status: 403,
117
+ detail: 'Forbidden',
118
+ object: 'problem',
119
+ }),
120
+ });
121
+ yield expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(new Error('Failed to upsert remote: Not allowed to mount remote outside of project content path: /docs'));
122
+ }));
123
+ it('should throw statusText error if response is not ok', () => __awaiter(void 0, void 0, void 0, function* () {
124
+ mockFetchResponse({
125
+ ok: false,
126
+ statusText: 'Not found',
127
+ json: jest.fn().mockResolvedValue({
128
+ unknownField: 'unknown-error',
129
+ }),
130
+ });
131
+ yield expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(new Error('Failed to upsert remote: Not found'));
132
+ }));
133
+ });
134
+ describe('push()', () => {
135
+ const testRemoteId = 'test-remote-id';
136
+ const pushPayload = {
137
+ remoteId: testRemoteId,
138
+ commit: {
139
+ message: 'test-message',
140
+ author: {
141
+ name: 'test-name',
142
+ email: 'test-email',
143
+ },
144
+ branchName: 'test-branch-name',
145
+ },
146
+ };
147
+ const filesMock = [{ path: 'some-file.yaml', stream: Buffer.from('fefef') }];
148
+ const responseMock = {
149
+ branchName: 'rem/cicd/rem_01he7sr6ys2agb7w0g9t7978fn-main',
150
+ hasChanges: true,
151
+ files: [
152
+ {
153
+ type: 'file',
154
+ name: 'some-file.yaml',
155
+ path: 'docs/remotes/some-file.yaml',
156
+ lastModified: 1698925132394.2993,
157
+ mimeType: 'text/yaml',
158
+ },
159
+ ],
160
+ commitSha: 'bb23a2f8e012ac0b7b9961b57fb40d8686b21b43',
161
+ outdated: false,
162
+ };
163
+ let apiClient;
164
+ beforeEach(() => {
165
+ apiClient = new api_client_1.ReuniteApiClient(testDomain, testToken);
166
+ });
167
+ it('should push to remote', () => __awaiter(void 0, void 0, void 0, function* () {
168
+ let passedFormData = new FormData();
169
+ node_fetch_1.default.mockImplementationOnce((_, options) => __awaiter(void 0, void 0, void 0, function* () {
170
+ passedFormData = options.body;
171
+ return {
172
+ ok: true,
173
+ json: jest.fn().mockResolvedValue(responseMock),
174
+ };
175
+ }));
176
+ const formData = new FormData();
177
+ formData.append('remoteId', testRemoteId);
178
+ formData.append('commit[message]', pushPayload.commit.message);
179
+ formData.append('commit[author][name]', pushPayload.commit.author.name);
180
+ formData.append('commit[author][email]', pushPayload.commit.author.email);
181
+ formData.append('commit[branchName]', pushPayload.commit.branchName);
182
+ formData.append('files[some-file.yaml]', filesMock[0].stream);
183
+ const result = yield apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock);
184
+ expect(node_fetch_1.default).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/pushes`, expect.objectContaining({
185
+ method: 'POST',
186
+ headers: {
187
+ Authorization: `Bearer ${testToken}`,
188
+ },
189
+ }));
190
+ expect(JSON.stringify(passedFormData).replace(new RegExp(passedFormData.getBoundary(), 'g'), '')).toEqual(JSON.stringify(formData).replace(new RegExp(formData.getBoundary(), 'g'), ''));
191
+ expect(result).toEqual(responseMock);
192
+ }));
193
+ it('should throw parsed error if response is not ok', () => __awaiter(void 0, void 0, void 0, function* () {
194
+ mockFetchResponse({
195
+ ok: false,
196
+ json: jest.fn().mockResolvedValue({
197
+ type: 'about:blank',
198
+ title: 'Cannot push to remote',
199
+ status: 403,
200
+ detail: 'Forbidden',
201
+ object: 'problem',
202
+ }),
203
+ });
204
+ yield expect(apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)).rejects.toThrow(new Error('Failed to push: Cannot push to remote'));
205
+ }));
206
+ it('should throw statusText error if response is not ok', () => __awaiter(void 0, void 0, void 0, function* () {
207
+ mockFetchResponse({
208
+ ok: false,
209
+ statusText: 'Not found',
210
+ json: jest.fn().mockResolvedValue({
211
+ unknownField: 'unknown-error',
212
+ }),
213
+ });
214
+ yield expect(apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)).rejects.toThrow(new Error('Failed to push: Not found'));
215
+ }));
216
+ });
217
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const domains_1 = require("../domains");
4
+ describe('getDomain()', () => {
5
+ it('should return the domain from environment variable', () => {
6
+ process.env.REDOCLY_DOMAIN = 'test-domain';
7
+ expect((0, domains_1.getDomain)()).toBe('test-domain');
8
+ });
9
+ it('should return the default domain if no domain provided', () => {
10
+ process.env.REDOCLY_DOMAIN = '';
11
+ expect((0, domains_1.getDomain)()).toBe('https://app.cloud.redocly.com');
12
+ });
13
+ });
@@ -0,0 +1,50 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import type { ReadStream } from 'fs';
4
+ import type { ListRemotesResponse, PushResponse, UpsertRemoteResponse } from './types';
5
+ declare class RemotesApiClient {
6
+ private readonly domain;
7
+ private readonly apiKey;
8
+ constructor(domain: string, apiKey: string);
9
+ private getParsedResponse;
10
+ getDefaultBranch(organizationId: string, projectId: string): Promise<string>;
11
+ upsert(organizationId: string, projectId: string, remote: {
12
+ mountPath: string;
13
+ mountBranchName: string;
14
+ }): Promise<UpsertRemoteResponse>;
15
+ push(organizationId: string, projectId: string, payload: PushPayload, files: {
16
+ path: string;
17
+ stream: ReadStream | Buffer;
18
+ }[]): Promise<PushResponse>;
19
+ getRemotesList(organizationId: string, projectId: string, mountPath: string): Promise<ListRemotesResponse>;
20
+ getPush({ organizationId, projectId, pushId, }: {
21
+ organizationId: string;
22
+ projectId: string;
23
+ pushId: string;
24
+ }): Promise<PushResponse>;
25
+ }
26
+ export declare class ReuniteApiClient {
27
+ domain: string;
28
+ private readonly apiKey;
29
+ remotes: RemotesApiClient;
30
+ constructor(domain: string, apiKey: string);
31
+ }
32
+ export type PushPayload = {
33
+ remoteId: string;
34
+ commit: {
35
+ message: string;
36
+ branchName: string;
37
+ sha?: string;
38
+ url?: string;
39
+ createdAt?: string;
40
+ namespace?: string;
41
+ repository?: string;
42
+ author: {
43
+ name: string;
44
+ email: string;
45
+ image?: string;
46
+ };
47
+ };
48
+ isMainBranch?: boolean;
49
+ };
50
+ export {};