@redocly/cli 0.0.0-snapshot.1737556585 → 0.0.0-snapshot.1737627998

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.
@@ -4,59 +4,26 @@ const openapi_core_1 = require("@redocly/openapi-core");
4
4
  const push_1 = require("../../commands/push");
5
5
  const login_1 = require("../../commands/login");
6
6
  const config_1 = require("../fixtures/config");
7
- const node_stream_1 = require("node:stream");
8
- // Mock fs operations
9
- jest.mock('fs', () => ({
10
- ...jest.requireActual('fs'),
11
- createReadStream: () => {
12
- const readable = new node_stream_1.Readable();
13
- readable.push('test data');
14
- readable.push(null);
15
- return readable;
16
- },
17
- statSync: () => ({ size: 9 }),
18
- readFileSync: () => Buffer.from('test data'),
19
- existsSync: () => false,
20
- readdirSync: () => [],
7
+ jest.mock('fs');
8
+ jest.mock('node-fetch', () => ({
9
+ default: jest.fn(() => ({
10
+ ok: true,
11
+ json: jest.fn().mockResolvedValue({}),
12
+ })),
21
13
  }));
22
- openapi_core_1.getMergedConfig.mockImplementation((config) => config);
23
- // Mock OpenAPI core
24
14
  jest.mock('@redocly/openapi-core');
25
15
  jest.mock('../../commands/login');
26
16
  jest.mock('../../utils/miscellaneous');
17
+ openapi_core_1.getMergedConfig.mockImplementation((config) => config);
27
18
  const mockPromptClientToken = login_1.promptClientToken;
28
19
  describe('push-with-region', () => {
29
20
  const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
30
21
  redoclyClient.isAuthorizedWithRedoclyByRegion = jest.fn().mockResolvedValue(false);
31
- const originalFetch = fetch;
32
22
  beforeAll(() => {
33
- // Mock global fetch
34
- global.fetch = jest.fn(() => Promise.resolve({
35
- ok: true,
36
- status: 200,
37
- json: () => Promise.resolve({}),
38
- headers: new Headers(),
39
- statusText: 'OK',
40
- redirected: false,
41
- type: 'default',
42
- url: '',
43
- clone: () => ({}),
44
- body: new ReadableStream(),
45
- bodyUsed: false,
46
- arrayBuffer: async () => new ArrayBuffer(0),
47
- blob: async () => new Blob(),
48
- formData: async () => new FormData(),
49
- text: async () => '',
50
- }));
51
- });
52
- afterAll(() => {
53
- global.fetch = originalFetch;
54
- });
55
- beforeEach(() => {
56
23
  jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
57
24
  });
58
25
  it('should call login with default domain when region is US', async () => {
59
- redoclyClient.domain = 'redocly.com';
26
+ redoclyClient.domain = 'redoc.ly';
60
27
  await (0, push_1.handlePush)({
61
28
  argv: {
62
29
  upsert: true,
@@ -72,8 +39,6 @@ describe('push-with-region', () => {
72
39
  });
73
40
  it('should call login with EU domain when region is EU', async () => {
74
41
  redoclyClient.domain = 'eu.redocly.com';
75
- // Update config for EU region
76
- const euConfig = { ...config_1.ConfigFixture, region: 'eu' };
77
42
  await (0, push_1.handlePush)({
78
43
  argv: {
79
44
  upsert: true,
@@ -81,7 +46,7 @@ describe('push-with-region', () => {
81
46
  destination: '@org/my-api@1.0.0',
82
47
  branchName: 'test',
83
48
  },
84
- config: euConfig,
49
+ config: config_1.ConfigFixture,
85
50
  version: 'cli-version',
86
51
  });
87
52
  expect(mockPromptClientToken).toBeCalledTimes(1);
@@ -6,54 +6,21 @@ const miscellaneous_1 = require("../../utils/miscellaneous");
6
6
  const push_1 = require("../../commands/push");
7
7
  const config_1 = require("../fixtures/config");
8
8
  const colorette_1 = require("colorette");
9
- const node_stream_1 = require("node:stream");
10
- // Mock fs operations
11
- jest.mock('fs', () => ({
12
- ...jest.requireActual('fs'),
13
- createReadStream: jest.fn(() => {
14
- const readable = new node_stream_1.Readable();
15
- readable.push('test data');
16
- readable.push(null);
17
- return readable;
18
- }),
19
- statSync: jest.fn(() => ({ isDirectory: () => false, size: 10 })),
20
- readFileSync: jest.fn(() => Buffer.from('test data')),
21
- existsSync: jest.fn(() => false),
22
- readdirSync: jest.fn(() => []),
9
+ jest.mock('fs');
10
+ jest.mock('node-fetch', () => ({
11
+ default: jest.fn(() => ({
12
+ ok: true,
13
+ json: jest.fn().mockResolvedValue({}),
14
+ })),
23
15
  }));
24
16
  jest.mock('@redocly/openapi-core');
25
17
  jest.mock('../../utils/miscellaneous');
26
- // Mock fetch
27
- const mockFetch = jest.fn(() => Promise.resolve({
28
- ok: true,
29
- status: 200,
30
- json: () => Promise.resolve({}),
31
- headers: new Headers(),
32
- statusText: 'OK',
33
- redirected: false,
34
- type: 'default',
35
- url: '',
36
- clone: () => ({}),
37
- body: null,
38
- bodyUsed: false,
39
- arrayBuffer: async () => new ArrayBuffer(0),
40
- blob: async () => new Blob(),
41
- formData: async () => new FormData(),
42
- text: async () => '',
43
- }));
44
- const originalFetch = global.fetch;
45
18
  openapi_core_1.getMergedConfig.mockImplementation((config) => config);
46
19
  describe('push', () => {
47
20
  const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
48
- beforeAll(() => {
49
- global.fetch = mockFetch;
50
- });
51
21
  beforeEach(() => {
52
22
  jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
53
23
  });
54
- afterAll(() => {
55
- global.fetch = originalFetch;
56
- });
57
24
  it('pushes definition', async () => {
58
25
  await (0, push_1.handlePush)({
59
26
  argv: {
@@ -2,31 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const abort_controller_1 = require("abort-controller");
4
4
  const fetch_with_timeout_1 = require("../utils/fetch-with-timeout");
5
+ const node_fetch_1 = require("node-fetch");
5
6
  const openapi_core_1 = require("@redocly/openapi-core");
6
7
  const https_proxy_agent_1 = require("https-proxy-agent");
8
+ jest.mock('node-fetch');
7
9
  jest.mock('@redocly/openapi-core');
8
- const signalInstance = new abort_controller_1.default().signal;
9
- const mockFetch = jest.fn(() => Promise.resolve({
10
- ok: true,
11
- status: 200,
12
- json: () => Promise.resolve({}),
13
- headers: new Headers(),
14
- statusText: 'OK',
15
- redirected: false,
16
- type: 'default',
17
- url: '',
18
- clone: () => ({}),
19
- body: null,
20
- bodyUsed: false,
21
- arrayBuffer: async () => new ArrayBuffer(0),
22
- blob: async () => new Blob(),
23
- formData: async () => new FormData(),
24
- text: async () => '',
25
- signal: signalInstance,
26
- dispatcher: undefined,
27
- }));
28
- const originalFetch = global.fetch;
29
- global.fetch = mockFetch;
30
10
  describe('fetchWithTimeout', () => {
31
11
  beforeAll(() => {
32
12
  // @ts-ignore
@@ -36,29 +16,29 @@ describe('fetchWithTimeout', () => {
36
16
  beforeEach(() => {
37
17
  openapi_core_1.getProxyAgent.mockReturnValueOnce(undefined);
38
18
  });
39
- afterAll(() => {
40
- global.fetch = originalFetch;
19
+ afterEach(() => {
20
+ jest.clearAllMocks();
41
21
  });
42
- it('should call fetch with signal', async () => {
22
+ it('should call node-fetch with signal', async () => {
43
23
  await (0, fetch_with_timeout_1.default)('url', { timeout: 1000 });
44
24
  expect(global.setTimeout).toHaveBeenCalledTimes(1);
45
- expect(global.fetch).toHaveBeenCalledWith('url', expect.objectContaining({
46
- signal: expect.any(AbortSignal),
47
- dispatcher: undefined,
48
- }));
25
+ expect(node_fetch_1.default).toHaveBeenCalledWith('url', {
26
+ signal: new abort_controller_1.default().signal,
27
+ agent: undefined,
28
+ });
49
29
  expect(global.clearTimeout).toHaveBeenCalledTimes(1);
50
30
  });
51
- it('should call fetch with proxy agent', async () => {
31
+ it('should call node-fetch with proxy agent', async () => {
52
32
  openapi_core_1.getProxyAgent.mockRestore();
53
33
  const proxyAgent = new https_proxy_agent_1.HttpsProxyAgent('http://localhost');
54
34
  openapi_core_1.getProxyAgent.mockReturnValueOnce(proxyAgent);
55
35
  await (0, fetch_with_timeout_1.default)('url');
56
- expect(global.fetch).toHaveBeenCalledWith('url', { dispatcher: proxyAgent });
36
+ expect(node_fetch_1.default).toHaveBeenCalledWith('url', { agent: proxyAgent });
57
37
  });
58
- it('should call fetch without signal when timeout is not passed', async () => {
38
+ it('should call node-fetch without signal when timeout is not passed', async () => {
59
39
  await (0, fetch_with_timeout_1.default)('url');
60
40
  expect(global.setTimeout).not.toHaveBeenCalled();
61
- expect(global.fetch).toHaveBeenCalledWith('url', { agent: undefined });
41
+ expect(node_fetch_1.default).toHaveBeenCalledWith('url', { agent: undefined });
62
42
  expect(global.clearTimeout).not.toHaveBeenCalled();
63
43
  });
64
44
  });
@@ -6,19 +6,11 @@ const wrapper_1 = require("../wrapper");
6
6
  const lint_1 = require("../commands/lint");
7
7
  const push_1 = require("../commands/push");
8
8
  const openapi_core_1 = require("@redocly/openapi-core");
9
- const mockFetch = jest.fn();
10
- const originalFetch = global.fetch;
9
+ jest.mock('node-fetch');
11
10
  jest.mock('../utils/miscellaneous', () => ({
12
11
  sendTelemetry: jest.fn(),
13
12
  loadConfigAndHandleErrors: jest.fn(),
14
13
  }));
15
- beforeAll(() => {
16
- global.fetch = mockFetch;
17
- });
18
- afterAll(() => {
19
- jest.resetAllMocks();
20
- global.fetch = originalFetch;
21
- });
22
14
  jest.mock('../commands/lint', () => ({
23
15
  handleLint: jest.fn().mockImplementation(({ collectSpecData }) => {
24
16
  collectSpecData({ openapi: '3.1.0' });
@@ -1,18 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_fetch_1 = require("node-fetch");
4
+ const FormData = require("form-data");
3
5
  const colorette_1 = require("colorette");
4
6
  const api_client_1 = require("../api-client");
5
- const originalFetch = global.fetch;
6
- beforeAll(() => {
7
- // Reset fetch mock before each test
8
- global.fetch = jest.fn();
9
- });
10
- afterAll(() => {
11
- // Restore original fetch after each test
12
- global.fetch = originalFetch;
13
- });
7
+ jest.mock('node-fetch', () => ({
8
+ default: jest.fn(),
9
+ }));
14
10
  function mockFetchResponse(response) {
15
- global.fetch.mockResolvedValue(response);
11
+ node_fetch_1.default.mockResolvedValue(response);
16
12
  }
17
13
  describe('ApiClient', () => {
18
14
  const testToken = 'test-token';
@@ -35,7 +31,7 @@ describe('ApiClient', () => {
35
31
  }),
36
32
  });
37
33
  const result = await apiClient.remotes.getDefaultBranch(testOrg, testProject);
38
- expect(global.fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/source`, {
34
+ expect(node_fetch_1.default).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/source`, {
39
35
  method: 'GET',
40
36
  headers: {
41
37
  'Content-Type': 'application/json',
@@ -93,7 +89,7 @@ describe('ApiClient', () => {
93
89
  json: jest.fn().mockResolvedValue(responseMock),
94
90
  });
95
91
  const result = await apiClient.remotes.upsert(testOrg, testProject, remotePayload);
96
- expect(global.fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/remotes`, {
92
+ expect(node_fetch_1.default).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/remotes`, {
97
93
  method: 'POST',
98
94
  headers: {
99
95
  'Content-Type': 'application/json',
@@ -171,29 +167,29 @@ describe('ApiClient', () => {
171
167
  });
172
168
  it('should push to remote', async () => {
173
169
  let passedFormData = new FormData();
174
- fetch.mockImplementationOnce(async (_, options) => {
170
+ node_fetch_1.default.mockImplementationOnce(async (_, options) => {
175
171
  passedFormData = options.body;
176
172
  return {
177
173
  ok: true,
178
174
  json: jest.fn().mockResolvedValue(responseMock),
179
175
  };
180
176
  });
181
- const formData = new globalThis.FormData();
177
+ const formData = new FormData();
182
178
  formData.append('remoteId', testRemoteId);
183
179
  formData.append('commit[message]', pushPayload.commit.message);
184
180
  formData.append('commit[author][name]', pushPayload.commit.author.name);
185
181
  formData.append('commit[author][email]', pushPayload.commit.author.email);
186
182
  formData.append('commit[branchName]', pushPayload.commit.branchName);
187
- formData.append('files[some-file.yaml]', new Blob([filesMock[0].stream]));
183
+ formData.append('files[some-file.yaml]', filesMock[0].stream);
188
184
  const result = await apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock);
189
- expect(fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/pushes`, expect.objectContaining({
185
+ expect(node_fetch_1.default).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/pushes`, expect.objectContaining({
190
186
  method: 'POST',
191
187
  headers: {
192
188
  Authorization: `Bearer ${testToken}`,
193
189
  'user-agent': expectedUserAgent,
194
190
  },
195
191
  }));
196
- expect(passedFormData).toEqual(formData);
192
+ expect(JSON.stringify(passedFormData).replace(new RegExp(passedFormData.getBoundary(), 'g'), '')).toEqual(JSON.stringify(formData).replace(new RegExp(formData.getBoundary(), 'g'), ''));
197
193
  expect(result).toEqual(responseMock);
198
194
  });
199
195
  it('should throw parsed error if response is not ok', async () => {
@@ -1,6 +1,6 @@
1
1
  import { type FetchWithTimeoutOptions } from '../../utils/fetch-with-timeout';
2
+ import type { Response } from 'node-fetch';
2
3
  import type { ReadStream } from 'fs';
3
- import type { Readable } from 'node:stream';
4
4
  import type { ListRemotesResponse, PushResponse, UpsertRemoteResponse } from './types';
5
5
  interface BaseApiClient {
6
6
  request(url: string, options: FetchWithTimeoutOptions): Promise<Response>;
@@ -72,5 +72,4 @@ export type PushPayload = {
72
72
  };
73
73
  isMainBranch?: boolean;
74
74
  };
75
- export declare function streamToBuffer(stream: ReadStream | Readable): Promise<Buffer>;
76
75
  export {};
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ReuniteApi = exports.ReuniteApiError = void 0;
4
- exports.streamToBuffer = streamToBuffer;
5
4
  const colorette_1 = require("colorette");
5
+ const FormData = require("form-data");
6
6
  const fetch_with_timeout_1 = require("../../utils/fetch-with-timeout");
7
7
  class ReuniteApiError extends Error {
8
8
  constructor(message, status) {
@@ -120,7 +120,7 @@ class RemotesApi {
120
120
  }
121
121
  }
122
122
  async push(organizationId, projectId, payload, files) {
123
- const formData = new globalThis.FormData();
123
+ const formData = new FormData();
124
124
  formData.append('remoteId', payload.remoteId);
125
125
  formData.append('commit[message]', payload.commit.message);
126
126
  formData.append('commit[author][name]', payload.commit.author.name);
@@ -132,10 +132,7 @@ class RemotesApi {
132
132
  payload.commit.repository && formData.append('commit[repositoryId]', payload.commit.repository);
133
133
  payload.commit.createdAt && formData.append('commit[createdAt]', payload.commit.createdAt);
134
134
  for (const file of files) {
135
- const blob = Buffer.isBuffer(file.stream)
136
- ? new Blob([file.stream])
137
- : new Blob([await streamToBuffer(file.stream)]);
138
- formData.append(`files[${file.path}]`, blob, file.path);
135
+ formData.append(`files[${file.path}]`, file.stream);
139
136
  }
140
137
  payload.isMainBranch && formData.append('isMainBranch', 'true');
141
138
  try {
@@ -226,10 +223,3 @@ class ReuniteApi {
226
223
  }
227
224
  }
228
225
  exports.ReuniteApi = ReuniteApi;
229
- async function streamToBuffer(stream) {
230
- const chunks = [];
231
- for await (const chunk of stream) {
232
- chunks.push(chunk);
233
- }
234
- return Buffer.concat(chunks);
235
- }
@@ -7,6 +7,7 @@ exports.getDestinationProps = getDestinationProps;
7
7
  exports.getApiRoot = getApiRoot;
8
8
  const fs = require("fs");
9
9
  const path = require("path");
10
+ const node_fetch_1 = require("node-fetch");
10
11
  const perf_hooks_1 = require("perf_hooks");
11
12
  const colorette_1 = require("colorette");
12
13
  const crypto_1 = require("crypto");
@@ -15,7 +16,6 @@ const utils_1 = require("@redocly/openapi-core/lib/utils");
15
16
  const miscellaneous_1 = require("../utils/miscellaneous");
16
17
  const login_1 = require("./login");
17
18
  const push_1 = require("../cms/commands/push");
18
- const api_client_1 = require("../cms/api/api-client");
19
19
  const DEFAULT_VERSION = 'latest';
20
20
  exports.DESTINATION_REGEX =
21
21
  // eslint-disable-next-line no-useless-escape
@@ -279,23 +279,17 @@ function getApiRoot({ name, version, config: { apis }, }) {
279
279
  const api = apis?.[`${name}@${version}`] || (version === DEFAULT_VERSION && apis?.[name]);
280
280
  return api?.root;
281
281
  }
282
- async function uploadFileToS3(url, filePathOrBuffer) {
282
+ function uploadFileToS3(url, filePathOrBuffer) {
283
283
  const fileSizeInBytes = typeof filePathOrBuffer === 'string'
284
284
  ? fs.statSync(filePathOrBuffer).size
285
285
  : filePathOrBuffer.byteLength;
286
286
  const readStream = typeof filePathOrBuffer === 'string' ? fs.createReadStream(filePathOrBuffer) : filePathOrBuffer;
287
- const requestOptions = {
287
+ return (0, node_fetch_1.default)(url, {
288
288
  method: 'PUT',
289
289
  headers: {
290
290
  'Content-Length': fileSizeInBytes.toString(),
291
291
  },
292
- body: Buffer.isBuffer(readStream)
293
- ? new Blob([readStream])
294
- : new Blob([await (0, api_client_1.streamToBuffer)(readStream)]),
295
- };
296
- const proxyAgent = (0, openapi_core_1.getProxyAgent)();
297
- if (proxyAgent) {
298
- requestOptions.dispatcher = proxyAgent;
299
- }
300
- return fetch(url, requestOptions);
292
+ body: readStream,
293
+ agent: (0, openapi_core_1.getProxyAgent)(),
294
+ });
301
295
  }
@@ -1,6 +1,7 @@
1
+ import { type RequestInit } from 'node-fetch';
1
2
  export declare const DEFAULT_FETCH_TIMEOUT = 3000;
2
3
  export type FetchWithTimeoutOptions = RequestInit & {
3
4
  timeout?: number;
4
5
  };
5
- declare const _default: (url: string, { timeout, ...options }?: FetchWithTimeoutOptions) => Promise<Response>;
6
+ declare const _default: (url: string, { timeout, ...options }?: FetchWithTimeoutOptions) => Promise<import("node-fetch").Response>;
6
7
  export default _default;
@@ -1,23 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_FETCH_TIMEOUT = void 0;
4
+ const node_fetch_1 = require("node-fetch");
5
+ const abort_controller_1 = require("abort-controller");
4
6
  const openapi_core_1 = require("@redocly/openapi-core");
5
7
  exports.DEFAULT_FETCH_TIMEOUT = 3000;
6
8
  exports.default = async (url, { timeout, ...options } = {}) => {
7
9
  if (!timeout) {
8
- return fetch(url, {
10
+ return (0, node_fetch_1.default)(url, {
9
11
  ...options,
10
- dispatcher: (0, openapi_core_1.getProxyAgent)(),
12
+ agent: (0, openapi_core_1.getProxyAgent)(),
11
13
  });
12
14
  }
13
- const controller = new globalThis.AbortController();
15
+ const controller = new abort_controller_1.default();
14
16
  const timeoutId = setTimeout(() => {
15
17
  controller.abort();
16
18
  }, timeout);
17
- const res = await fetch(url, {
19
+ const res = await (0, node_fetch_1.default)(url, {
18
20
  signal: controller.signal,
19
21
  ...options,
20
- dispatcher: (0, openapi_core_1.getProxyAgent)(),
22
+ agent: (0, openapi_core_1.getProxyAgent)(),
21
23
  });
22
24
  clearTimeout(timeoutId);
23
25
  return res;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/cli",
3
- "version": "0.0.0-snapshot.1737556585",
3
+ "version": "0.0.0-snapshot.1737627998",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -8,8 +8,8 @@
8
8
  "redocly": "bin/cli.js"
9
9
  },
10
10
  "engines": {
11
- "node": ">=18.17.0",
12
- "npm": ">=10.8.2"
11
+ "node": ">=14.19.0",
12
+ "npm": ">=7.0.0"
13
13
  },
14
14
  "engineStrict": true,
15
15
  "scripts": {
@@ -36,7 +36,7 @@
36
36
  "Roman Hotsiy <roman@redocly.com> (https://redocly.com/)"
37
37
  ],
38
38
  "dependencies": {
39
- "@redocly/openapi-core": "0.0.0-snapshot.1737556585",
39
+ "@redocly/openapi-core": "0.0.0-snapshot.1737627998",
40
40
  "abort-controller": "^3.0.0",
41
41
  "chokidar": "^3.5.1",
42
42
  "colorette": "^1.2.0",
@@ -46,6 +46,7 @@
46
46
  "glob": "^7.1.6",
47
47
  "handlebars": "^4.7.6",
48
48
  "mobx": "^6.0.4",
49
+ "node-fetch": "^2.6.1",
49
50
  "pluralize": "^8.0.0",
50
51
  "react": "^17.0.0 || ^18.2.0",
51
52
  "react-dom": "^17.0.0 || ^18.2.0",