@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
@@ -0,0 +1,26 @@
1
+ import { resolve } from 'path';
2
+ import { homedir } from 'os';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { isNotEmptyObject } from '@redocly/openapi-core/lib/utils';
5
+ import { TOKEN_FILENAME } from '@redocly/openapi-core/lib/redocly';
6
+
7
+ function readCredentialsFile(credentialsPath: string) {
8
+ return existsSync(credentialsPath) ? JSON.parse(readFileSync(credentialsPath, 'utf-8')) : {};
9
+ }
10
+
11
+ export function getApiKeys(domain: string) {
12
+ const apiKey = process.env.REDOCLY_AUTHORIZATION;
13
+
14
+ if (apiKey) {
15
+ return apiKey;
16
+ }
17
+
18
+ const credentialsPath = resolve(homedir(), TOKEN_FILENAME);
19
+ const credentials = readCredentialsFile(credentialsPath);
20
+
21
+ if (isNotEmptyObject(credentials) && credentials[domain]) {
22
+ return credentials[domain];
23
+ }
24
+
25
+ throw new Error('No api key provided, please use environment variable REDOCLY_AUTHORIZATION.');
26
+ }
@@ -0,0 +1,11 @@
1
+ const DEFAULT_DOMAIN = 'https://app.cloud.redocly.com';
2
+
3
+ export function getDomain(): string {
4
+ const domain = process.env.REDOCLY_DOMAIN;
5
+
6
+ if (domain) {
7
+ return domain;
8
+ }
9
+
10
+ return DEFAULT_DOMAIN;
11
+ }
@@ -0,0 +1,3 @@
1
+ export * from './api-client';
2
+ export * from './domains';
3
+ export * from './api-keys';
@@ -0,0 +1,101 @@
1
+ export type ProjectSourceResponse = {
2
+ branchName: string;
3
+ contentPath: string;
4
+ isInternal: boolean;
5
+ };
6
+
7
+ export type UpsertRemoteResponse = {
8
+ id: string;
9
+ type: 'CICD';
10
+ mountPath: string;
11
+ mountBranchName: string;
12
+ organizationId: string;
13
+ projectId: string;
14
+ };
15
+
16
+ export type ListRemotesResponse = {
17
+ object: 'list';
18
+ page: {
19
+ endCursor: string;
20
+ startCursor: string;
21
+ haxNextPage: boolean;
22
+ hasPrevPage: boolean;
23
+ limit: number;
24
+ total: number;
25
+ };
26
+ items: Remote[];
27
+ };
28
+
29
+ export type Remote = {
30
+ mountPath: string;
31
+ type: string;
32
+ autoSync: boolean;
33
+ autoMerge: boolean;
34
+ createdAt: string;
35
+ updatedAt: string;
36
+ providerType: string;
37
+ namespaceId: string;
38
+ repositoryId: string;
39
+ projectId: string;
40
+ mountBranchName: string;
41
+ contentPath: string;
42
+ credentialId: string;
43
+ branchName: string;
44
+ contentType: string;
45
+ id: string;
46
+ };
47
+
48
+ export type PushResponse = {
49
+ id: string;
50
+ remoteId: string;
51
+ commit: {
52
+ message: string;
53
+ branchName: string;
54
+ sha: string | null;
55
+ url: string | null;
56
+ createdAt: string | null;
57
+ namespace: string | null;
58
+ repository: string | null;
59
+ author: {
60
+ name: string;
61
+ email: string;
62
+ image: string | null;
63
+ };
64
+ };
65
+ remote: {
66
+ commits: {
67
+ branchName: string;
68
+ sha: string;
69
+ }[];
70
+ };
71
+ hasChanges: boolean;
72
+ isOutdated: boolean;
73
+ isMainBranch: boolean;
74
+ status: PushStatusResponse;
75
+ };
76
+
77
+ type DeploymentStatusResponse = {
78
+ deploy: {
79
+ url: string | null;
80
+ status: DeploymentStatus;
81
+ };
82
+ scorecard: ScorecardItem[];
83
+ };
84
+
85
+ export type PushStatusResponse = {
86
+ preview: DeploymentStatusResponse;
87
+ production: DeploymentStatusResponse;
88
+ };
89
+
90
+ export type ScorecardItem = {
91
+ name: string;
92
+ status: PushStatusBase;
93
+ description: string;
94
+ url: string;
95
+ };
96
+
97
+ export type PushStatusBase = 'pending' | 'success' | 'running' | 'failed';
98
+
99
+ // export type BuildStatus = PushStatusBase | 'NOT_STARTED' | 'QUEUED';
100
+
101
+ export type DeploymentStatus = 'skipped' | PushStatusBase;
@@ -0,0 +1,212 @@
1
+ import { handlePushStatus } from '../push-status';
2
+ import { PushResponse } from '../../api/types';
3
+ import { exitWithError } from '../../../utils/miscellaneous';
4
+
5
+ const remotes = {
6
+ getPush: jest.fn(),
7
+ getRemotesList: jest.fn(),
8
+ };
9
+
10
+ jest.mock('../../../utils/miscellaneous');
11
+
12
+ jest.mock('colorette', () => ({
13
+ green: (str: string) => str,
14
+ yellow: (str: string) => str,
15
+ red: (str: string) => str,
16
+ gray: (str: string) => str,
17
+ magenta: (str: string) => str,
18
+ cyan: (str: string) => str,
19
+ }));
20
+
21
+ jest.mock('../../api', () => ({
22
+ ...jest.requireActual('../../api'),
23
+ ReuniteApiClient: jest.fn().mockImplementation(function (this: any, ...args) {
24
+ this.remotes = remotes;
25
+ }),
26
+ }));
27
+
28
+ describe('handlePushStatus()', () => {
29
+ const mockConfig = { apis: {} } as any;
30
+
31
+ const pushResponseStub = {
32
+ hasChanges: true,
33
+ status: {
34
+ preview: {
35
+ scorecard: [],
36
+ deploy: {
37
+ url: 'https://test-url',
38
+ status: 'success',
39
+ },
40
+ },
41
+ production: {
42
+ scorecard: [],
43
+ deploy: {
44
+ url: 'https://test-url',
45
+ status: 'success',
46
+ },
47
+ },
48
+ },
49
+ } as unknown as PushResponse;
50
+
51
+ beforeEach(() => {
52
+ jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
53
+ jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
54
+ });
55
+
56
+ afterEach(() => {
57
+ jest.clearAllMocks();
58
+ });
59
+
60
+ it('should throw error if organization not provided', async () => {
61
+ await handlePushStatus(
62
+ {
63
+ domain: 'test-domain',
64
+ organization: '',
65
+ project: 'test-project',
66
+ pushId: 'test-push-id',
67
+ 'max-execution-time': 1000,
68
+ },
69
+ mockConfig
70
+ );
71
+
72
+ expect(exitWithError).toHaveBeenCalledWith(
73
+ "No organization provided, please use --organization option or specify the 'organization' field in the config file."
74
+ );
75
+ });
76
+
77
+ it('should return success push status for preview-build', async () => {
78
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
79
+ remotes.getPush.mockResolvedValueOnce(pushResponseStub);
80
+
81
+ await handlePushStatus(
82
+ {
83
+ domain: 'test-domain',
84
+ organization: 'test-org',
85
+ project: 'test-project',
86
+ pushId: 'test-push-id',
87
+ 'max-execution-time': 1000,
88
+ },
89
+ mockConfig
90
+ );
91
+ expect(process.stdout.write).toHaveBeenCalledTimes(1);
92
+ expect(process.stdout.write).toHaveBeenCalledWith(
93
+ '🚀 PREVIEW deployment succeeded.\nPreview URL: https://test-url\n'
94
+ );
95
+ });
96
+
97
+ it('should return success push status for preview and production builds', async () => {
98
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
99
+ remotes.getPush.mockResolvedValue({ ...pushResponseStub, isMainBranch: true });
100
+
101
+ await handlePushStatus(
102
+ {
103
+ domain: 'test-domain',
104
+ organization: 'test-org',
105
+ project: 'test-project',
106
+ pushId: 'test-push-id',
107
+ 'max-execution-time': 1000,
108
+ },
109
+ mockConfig
110
+ );
111
+ expect(process.stdout.write).toHaveBeenCalledTimes(2);
112
+ expect(process.stdout.write).toHaveBeenCalledWith(
113
+ '🚀 PREVIEW deployment succeeded.\nPreview URL: https://test-url\n'
114
+ );
115
+ expect(process.stdout.write).toHaveBeenCalledWith(
116
+ '🚀 PRODUCTION deployment succeeded.\nPreview URL: https://test-url\n'
117
+ );
118
+ });
119
+
120
+ it('should return failed push status for preview build', async () => {
121
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
122
+
123
+ remotes.getPush.mockResolvedValue({
124
+ isOutdated: false,
125
+ hasChanges: true,
126
+ status: {
127
+ preview: { deploy: { status: 'failed', url: 'https://test-url' }, scorecard: [] },
128
+ },
129
+ });
130
+
131
+ await handlePushStatus(
132
+ {
133
+ domain: 'test-domain',
134
+ organization: 'test-org',
135
+ project: 'test-project',
136
+ pushId: 'test-push-id',
137
+ 'max-execution-time': 1000,
138
+ },
139
+ mockConfig
140
+ );
141
+ expect(exitWithError).toHaveBeenCalledWith(
142
+ '❌ PREVIEW deployment failed.\nPreview URL: https://test-url'
143
+ );
144
+ });
145
+
146
+ it('should return success push status for preview build and print scorecards', async () => {
147
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
148
+
149
+ remotes.getPush.mockResolvedValue({
150
+ isOutdated: false,
151
+ hasChanges: true,
152
+ status: {
153
+ preview: {
154
+ deploy: { status: 'success', url: 'https://test-url' },
155
+ scorecard: [
156
+ {
157
+ name: 'test-name',
158
+ status: 'success',
159
+ description: 'test-description',
160
+ url: 'test-url',
161
+ },
162
+ ],
163
+ },
164
+ },
165
+ });
166
+
167
+ await handlePushStatus(
168
+ {
169
+ domain: 'test-domain',
170
+ organization: 'test-org',
171
+ project: 'test-project',
172
+ pushId: 'test-push-id',
173
+ 'max-execution-time': 1000,
174
+ },
175
+ mockConfig
176
+ );
177
+ expect(process.stdout.write).toHaveBeenCalledTimes(4);
178
+ expect(process.stdout.write).toHaveBeenCalledWith(
179
+ '🚀 PREVIEW deployment succeeded.\nPreview URL: https://test-url\n'
180
+ );
181
+ expect(process.stdout.write).toHaveBeenCalledWith('\nScorecard:');
182
+ expect(process.stdout.write).toHaveBeenCalledWith(
183
+ '\n Name: test-name\n Status: success\n URL: test-url\n Description: test-description\n'
184
+ );
185
+ expect(process.stdout.write).toHaveBeenCalledWith('\n');
186
+ });
187
+
188
+ it('should display message if there is no changes', async () => {
189
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
190
+
191
+ remotes.getPush.mockResolvedValueOnce({
192
+ isOutdated: false,
193
+ hasChanges: false,
194
+ status: {
195
+ preview: { deploy: { status: 'skipped', url: 'https://test-url' }, scorecard: [] },
196
+ },
197
+ });
198
+
199
+ await handlePushStatus(
200
+ {
201
+ domain: 'test-domain',
202
+ organization: 'test-org',
203
+ project: 'test-project',
204
+ pushId: 'test-push-id',
205
+ wait: true,
206
+ 'max-execution-time': 1000,
207
+ },
208
+ mockConfig
209
+ );
210
+ expect(process.stderr.write).toHaveBeenCalledWith('Files not uploaded. Reason: no changes.\n');
211
+ });
212
+ });
@@ -0,0 +1,293 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { handlePush } from '../push';
4
+ import { ReuniteApiClient } from '../../api';
5
+
6
+ const remotes = {
7
+ push: jest.fn(),
8
+ upsert: jest.fn(),
9
+ getDefaultBranch: jest.fn(),
10
+ };
11
+
12
+ jest.mock('@redocly/openapi-core', () => ({
13
+ slash: jest.fn().mockImplementation((p) => p),
14
+ }));
15
+
16
+ jest.mock('../../api', () => ({
17
+ ...jest.requireActual('../../api'),
18
+ ReuniteApiClient: jest.fn().mockImplementation(function (this: any, ...args) {
19
+ this.remotes = remotes;
20
+ }),
21
+ }));
22
+
23
+ describe('handlePush()', () => {
24
+ let pathResolveSpy: jest.SpyInstance;
25
+ let pathRelativeSpy: jest.SpyInstance;
26
+ let pathDirnameSpy: jest.SpyInstance;
27
+ let fsStatSyncSpy: jest.SpyInstance;
28
+ let fsReaddirSyncSpy: jest.SpyInstance;
29
+
30
+ beforeEach(() => {
31
+ remotes.getDefaultBranch.mockResolvedValueOnce('test-default-branch');
32
+ remotes.upsert.mockResolvedValueOnce({ id: 'test-remote-id' });
33
+ remotes.push.mockResolvedValueOnce({ branchName: 'uploaded-to-branch' });
34
+
35
+ jest.spyOn(fs, 'createReadStream').mockReturnValue('stream' as any);
36
+
37
+ pathResolveSpy = jest.spyOn(path, 'resolve');
38
+ pathRelativeSpy = jest.spyOn(path, 'relative');
39
+ pathDirnameSpy = jest.spyOn(path, 'dirname');
40
+ fsStatSyncSpy = jest.spyOn(fs, 'statSync');
41
+ fsReaddirSyncSpy = jest.spyOn(fs, 'readdirSync');
42
+ });
43
+
44
+ afterEach(() => {
45
+ pathResolveSpy.mockRestore();
46
+ pathRelativeSpy.mockRestore();
47
+ pathDirnameSpy.mockRestore();
48
+ fsStatSyncSpy.mockRestore();
49
+ fsReaddirSyncSpy.mockRestore();
50
+ });
51
+
52
+ it('should upload files', async () => {
53
+ const mockConfig = { apis: {} } as any;
54
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
55
+
56
+ fsStatSyncSpy.mockReturnValueOnce({
57
+ isDirectory() {
58
+ return false;
59
+ },
60
+ } as any);
61
+
62
+ pathResolveSpy.mockImplementationOnce((p) => p);
63
+ pathRelativeSpy.mockImplementationOnce((_, p) => p);
64
+ pathDirnameSpy.mockImplementation((_: string) => '.');
65
+
66
+ await handlePush(
67
+ {
68
+ domain: 'test-domain',
69
+ 'mount-path': 'test-mount-path',
70
+ organization: 'test-org',
71
+ project: 'test-project',
72
+ branch: 'test-branch',
73
+ namespace: 'test-namespace',
74
+ repository: 'test-repository',
75
+ 'commit-sha': 'test-commit-sha',
76
+ 'commit-url': 'test-commit-url',
77
+ 'default-branch': 'test-branch',
78
+ 'created-at': 'test-created-at',
79
+ author: 'TestAuthor <test-author@mail.com>',
80
+ message: 'Test message',
81
+ files: ['test-file'],
82
+ 'max-execution-time': 10,
83
+ },
84
+ mockConfig
85
+ );
86
+
87
+ expect(remotes.getDefaultBranch).toHaveBeenCalledWith('test-org', 'test-project');
88
+ expect(remotes.upsert).toHaveBeenCalledWith('test-org', 'test-project', {
89
+ mountBranchName: 'test-default-branch',
90
+ mountPath: 'test-mount-path',
91
+ });
92
+ expect(remotes.push).toHaveBeenCalledWith(
93
+ 'test-org',
94
+ 'test-project',
95
+ {
96
+ isMainBranch: true,
97
+ remoteId: 'test-remote-id',
98
+ commit: {
99
+ message: 'Test message',
100
+ branchName: 'test-branch',
101
+ createdAt: 'test-created-at',
102
+ namespace: 'test-namespace',
103
+ repository: 'test-repository',
104
+ sha: 'test-commit-sha',
105
+ url: 'test-commit-url',
106
+ author: {
107
+ name: 'TestAuthor',
108
+ email: 'test-author@mail.com',
109
+ },
110
+ },
111
+ },
112
+ [
113
+ {
114
+ path: 'test-file',
115
+ stream: 'stream',
116
+ },
117
+ ]
118
+ );
119
+ });
120
+
121
+ it('should collect files from directory and preserve file structure', async () => {
122
+ const mockConfig = { apis: {} } as any;
123
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
124
+
125
+ /*
126
+ ├── app
127
+ │ ├── index.html
128
+ ├── openapi.yaml
129
+ └── some-ref.yaml
130
+ */
131
+
132
+ fsStatSyncSpy.mockImplementation(
133
+ (filePath) =>
134
+ ({
135
+ isDirectory() {
136
+ return filePath === 'test-folder' || filePath === 'test-folder/app';
137
+ },
138
+ } as any)
139
+ );
140
+
141
+ fsReaddirSyncSpy.mockImplementation((dirPath): any => {
142
+ if (dirPath === 'test-folder') {
143
+ return ['app', 'another-ref.yaml', 'openapi.yaml'];
144
+ }
145
+
146
+ if (dirPath === 'test-folder/app') {
147
+ return ['index.html'];
148
+ }
149
+
150
+ throw new Error('Not a directory');
151
+ });
152
+
153
+ await handlePush(
154
+ {
155
+ domain: 'test-domain',
156
+ 'mount-path': 'test-mount-path',
157
+ organization: 'test-org',
158
+ project: 'test-project',
159
+ branch: 'test-branch',
160
+ author: 'TestAuthor <test-author@mail.com>',
161
+ message: 'Test message',
162
+ 'default-branch': 'main',
163
+ files: ['test-folder'],
164
+ 'max-execution-time': 10,
165
+ },
166
+ mockConfig
167
+ );
168
+
169
+ expect(remotes.push).toHaveBeenCalledWith(
170
+ expect.anything(),
171
+ expect.anything(),
172
+ expect.anything(),
173
+ [
174
+ {
175
+ path: 'app/index.html',
176
+ stream: 'stream',
177
+ },
178
+
179
+ {
180
+ path: 'another-ref.yaml',
181
+ stream: 'stream',
182
+ },
183
+ {
184
+ path: 'openapi.yaml',
185
+ stream: 'stream',
186
+ },
187
+ ]
188
+ );
189
+ });
190
+
191
+ it('should not upload files if no files passed', async () => {
192
+ const mockConfig = { apis: {} } as any;
193
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
194
+
195
+ await handlePush(
196
+ {
197
+ domain: 'test-domain',
198
+ 'mount-path': 'test-mount-path',
199
+ organization: 'test-org',
200
+ project: 'test-project',
201
+ branch: 'test-branch',
202
+ author: 'TestAuthor <test-author@mail.com>',
203
+ message: 'Test message',
204
+ 'default-branch': 'main',
205
+ files: [],
206
+ 'max-execution-time': 10,
207
+ },
208
+ mockConfig
209
+ );
210
+
211
+ expect(remotes.getDefaultBranch).not.toHaveBeenCalled();
212
+ expect(remotes.upsert).not.toHaveBeenCalled();
213
+ expect(remotes.push).not.toHaveBeenCalled();
214
+ });
215
+
216
+ it('should get organization from config if not passed', async () => {
217
+ const mockConfig = { organization: 'test-org-from-config', apis: {} } as any;
218
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
219
+
220
+ fsStatSyncSpy.mockReturnValueOnce({
221
+ isDirectory() {
222
+ return false;
223
+ },
224
+ } as any);
225
+
226
+ pathResolveSpy.mockImplementationOnce((p) => p);
227
+ pathRelativeSpy.mockImplementationOnce((_, p) => p);
228
+ pathDirnameSpy.mockImplementation((_: string) => '.');
229
+
230
+ await handlePush(
231
+ {
232
+ domain: 'test-domain',
233
+ 'mount-path': 'test-mount-path',
234
+ project: 'test-project',
235
+ branch: 'test-branch',
236
+ author: 'TestAuthor <test-author@mail.com>',
237
+ message: 'Test message',
238
+ files: ['test-file'],
239
+ 'default-branch': 'main',
240
+ 'max-execution-time': 10,
241
+ },
242
+ mockConfig
243
+ );
244
+
245
+ expect(remotes.getDefaultBranch).toHaveBeenCalledWith(
246
+ 'test-org-from-config',
247
+ expect.anything()
248
+ );
249
+ expect(remotes.upsert).toHaveBeenCalledWith(
250
+ 'test-org-from-config',
251
+ expect.anything(),
252
+ expect.anything()
253
+ );
254
+ expect(remotes.push).toHaveBeenCalledWith(
255
+ 'test-org-from-config',
256
+ expect.anything(),
257
+ expect.anything(),
258
+ expect.anything()
259
+ );
260
+ });
261
+
262
+ it('should get domain from env if not passed', async () => {
263
+ const mockConfig = { organization: 'test-org-from-config', apis: {} } as any;
264
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
265
+ process.env.REDOCLY_DOMAIN = 'test-domain-from-env';
266
+
267
+ fsStatSyncSpy.mockReturnValueOnce({
268
+ isDirectory() {
269
+ return false;
270
+ },
271
+ } as any);
272
+
273
+ pathResolveSpy.mockImplementationOnce((p) => p);
274
+ pathRelativeSpy.mockImplementationOnce((_, p) => p);
275
+ pathDirnameSpy.mockImplementation((_: string) => '.');
276
+
277
+ await handlePush(
278
+ {
279
+ 'mount-path': 'test-mount-path',
280
+ project: 'test-project',
281
+ branch: 'test-branch',
282
+ 'default-branch': 'main',
283
+ author: 'TestAuthor <test-author@mail.com>',
284
+ message: 'Test message',
285
+ files: ['test-file'],
286
+ 'max-execution-time': 10,
287
+ },
288
+ mockConfig
289
+ );
290
+
291
+ expect(ReuniteApiClient).toBeCalledWith('test-domain-from-env', 'test-api-key');
292
+ });
293
+ });