@redocly/openapi-core 1.0.0-beta.68 → 1.0.0-beta.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/lint.ts CHANGED
@@ -72,6 +72,7 @@ export async function lintDocument(opts: {
72
72
  const ctx: WalkContext = {
73
73
  problems: [],
74
74
  oasVersion: oasVersion,
75
+ visitorsData: {},
75
76
  };
76
77
 
77
78
  const preprocessors = initRules(rules as any, config, 'preprocessors', oasVersion);
@@ -101,6 +102,7 @@ export async function lintConfig(opts: {
101
102
  const ctx: WalkContext = {
102
103
  problems: [],
103
104
  oasVersion: OasVersion.Version3_0,
105
+ visitorsData: {},
104
106
  };
105
107
  const config = new LintConfig({
106
108
  plugins: [defaultPlugin],
@@ -2,15 +2,17 @@ import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
2
2
  import { resolve } from 'path';
3
3
  import { homedir } from 'os';
4
4
  import { yellow, red, green, gray } from 'colorette';
5
- import { query } from './query';
5
+ import { RegistryApi } from './registry-api';
6
6
 
7
7
  const TOKEN_FILENAME = '.redocly-config.json';
8
8
 
9
9
  export class RedoclyClient {
10
10
  private accessToken: string | undefined;
11
+ registryApi: RegistryApi;
11
12
 
12
13
  constructor() {
13
14
  this.loadToken();
15
+ this.registryApi = new RegistryApi(this.accessToken);
14
16
  }
15
17
 
16
18
  hasToken(): boolean {
@@ -36,9 +38,8 @@ export class RedoclyClient {
36
38
 
37
39
  async verifyToken(accessToken: string, verbose: boolean = false): Promise<boolean> {
38
40
  if (!accessToken) return false;
39
- const authDetails = await RedoclyClient.authorize(accessToken, { verbose });
40
- if (!authDetails) return false;
41
- return true;
41
+
42
+ return this.registryApi.setAccessToken(accessToken).authStatus(verbose);
42
43
  }
43
44
 
44
45
  async getAuthorizationHeader(): Promise<string | undefined> {
@@ -83,198 +84,20 @@ export class RedoclyClient {
83
84
  }
84
85
  process.stdout.write('Logged out from the Redocly account. ✋\n');
85
86
  }
87
+ }
86
88
 
87
- async query(queryString: string, parameters = {}, headers = {}) {
88
- return query(queryString, parameters, {
89
- Authorization: this.accessToken,
90
- ...headers,
91
- });
92
- }
93
-
94
- static async authorize(accessToken: string, options: { queryName?: string; verbose?: boolean }) {
95
- const { queryName = '', verbose = false } = options;
96
- try {
97
- const queryStr = `query ${queryName}{ viewer { id } }`;
98
-
99
- return await query(queryStr, {}, { Authorization: accessToken });
100
- } catch (e) {
101
- if (verbose) console.log(e);
102
- return null;
103
- }
104
- }
105
-
106
- async updateDependencies(dependencies: string[] | undefined): Promise<void> {
107
- const definitionId = process.env.DEFINITION;
108
- const versionId = process.env.DEFINITION;
109
- const branchId = process.env.BRANCH;
110
-
111
- if (!definitionId || !versionId || !branchId) return;
112
-
113
- await this.query(
114
- `
115
- mutation UpdateBranchDependenciesFromURLs(
116
- $urls: [String!]!
117
- $definitionId: Int!
118
- $versionId: Int!
119
- $branchId: Int!
120
- ) {
121
- updateBranchDependenciesFromURLs(
122
- definitionId: $definitionId
123
- versionId: $versionId
124
- branchId: $branchId
125
- urls: $urls
126
- ) {
127
- branchName
128
- }
129
- }
130
- `,
131
- {
132
- urls: dependencies || [],
133
- definitionId: parseInt(definitionId, 10),
134
- versionId: parseInt(versionId, 10),
135
- branchId: parseInt(branchId, 10),
136
- },
137
- );
138
- }
139
-
140
- updateDefinitionVersion(definitionId: number, versionId: number, updatePatch: object): Promise<void> {
141
- return this.query(`
142
- mutation UpdateDefinitionVersion($definitionId: Int!, $versionId: Int!, $updatePatch: DefinitionVersionPatch!) {
143
- updateDefinitionVersionByDefinitionIdAndId(input: {definitionId: $definitionId, id: $versionId, patch: $updatePatch}) {
144
- definitionVersion {
145
- ...VersionDetails
146
- __typename
147
- }
148
- __typename
149
- }
150
- }
151
-
152
- fragment VersionDetails on DefinitionVersion {
153
- id
154
- nodeId
155
- uuid
156
- definitionId
157
- name
158
- description
159
- sourceType
160
- source
161
- registryAccess
162
- __typename
163
- }
164
- `,
165
- {
166
- definitionId,
167
- versionId,
168
- updatePatch,
169
- },
170
- );
171
- }
172
-
173
- getOrganizationId(organizationId: string) {
174
- return this.query(`
175
- query ($organizationId: String!) {
176
- organizationById(id: $organizationId) {
177
- id
178
- }
179
- }
180
- `, {
181
- organizationId
182
- });
183
- }
184
-
185
- getDefinitionByName(name: string, organizationId: string) {
186
- return this.query(`
187
- query ($name: String!, $organizationId: String!) {
188
- definition: definitionByOrganizationIdAndName(name: $name, organizationId: $organizationId) {
189
- id
190
- }
191
- }
192
- `, {
193
- name,
194
- organizationId
195
- });
196
- }
197
-
198
- createDefinition(organizationId: string, name: string) {
199
- return this.query(`
200
- mutation CreateDefinition($organizationId: String!, $name: String!) {
201
- def: createDefinition(input: {organizationId: $organizationId, name: $name }) {
202
- definition {
203
- id
204
- nodeId
205
- name
206
- }
207
- }
208
- }
209
- `, {
210
- organizationId,
211
- name
212
- })
213
- }
214
-
215
- createDefinitionVersion(definitionId: string, name: string, sourceType: string, source: any) {
216
- return this.query(`
217
- mutation CreateVersion($definitionId: Int!, $name: String!, $sourceType: DvSourceType!, $source: JSON) {
218
- createDefinitionVersion(input: {definitionId: $definitionId, name: $name, sourceType: $sourceType, source: $source }) {
219
- definitionVersion {
220
- id
221
- }
222
- }
223
- }
224
- `, {
225
- definitionId,
226
- name,
227
- sourceType,
228
- source
229
- });
230
- }
231
-
232
- getSignedUrl(organizationId: string, filesHash: string, fileName: string) {
233
- return this.query(`
234
- query ($organizationId: String!, $filesHash: String!, $fileName: String!) {
235
- signFileUploadCLI(organizationId: $organizationId, filesHash: $filesHash, fileName: $fileName) {
236
- signedFileUrl
237
- uploadedFilePath
238
- }
239
- }
240
- `, {
241
- organizationId,
242
- filesHash,
243
- fileName
244
- })
245
- }
246
-
247
- getDefinitionVersion(organizationId: string, definitionName: string, versionName: string) {
248
- return this.query(`
249
- query ($organizationId: String!, $definitionName: String!, $versionName: String!) {
250
- version: definitionVersionByOrganizationDefinitionAndName(organizationId: $organizationId, definitionName: $definitionName, versionName: $versionName) {
251
- id
252
- definitionId
253
- defaultBranch {
254
- name
255
- }
256
- }
257
- }
258
- `, {
259
- organizationId,
260
- definitionName,
261
- versionName
262
- });
263
- }
264
-
265
- static isRegistryURL(link: string): boolean {
266
- const domain = process.env.REDOCLY_DOMAIN || 'redoc.ly';
267
- if (!link.startsWith(`https://api.${domain}/registry/`)) return false;
268
- const registryPath = link.replace(`https://api.${domain}/registry/`, '');
89
+ export function isRedoclyRegistryURL(link: string): boolean {
90
+ const domain = process.env.REDOCLY_DOMAIN || 'redoc.ly';
91
+ if (!link.startsWith(`https://api.${domain}/registry/`)) return false;
92
+ const registryPath = link.replace(`https://api.${domain}/registry/`, '');
269
93
 
270
- const pathParts = registryPath.split('/');
94
+ const pathParts = registryPath.split('/');
271
95
 
272
- // we can be sure, that there is job UUID present
273
- // (org, definition, version, bundle, branch, job, "openapi.yaml" 🤦‍♂️)
274
- // so skip this link.
275
- // FIXME
276
- if (pathParts.length === 7) return false;
96
+ // we can be sure, that there is job UUID present
97
+ // (org, definition, version, bundle, branch, job, "openapi.yaml" 🤦‍♂️)
98
+ // so skip this link.
99
+ // FIXME
100
+ if (pathParts.length === 7) return false;
277
101
 
278
- return true;
279
- }
102
+ return true;
280
103
  }
@@ -0,0 +1,31 @@
1
+ export namespace RegistryApiTypes {
2
+ interface VersionParams {
3
+ organizationId: string;
4
+ name: string;
5
+ version: string;
6
+ }
7
+
8
+ export interface PrepareFileuploadParams extends VersionParams {
9
+ filesHash: string;
10
+ filename: string;
11
+ isUpsert?: boolean;
12
+ }
13
+
14
+ export interface PushApiParams extends VersionParams {
15
+ rootFilePath: string;
16
+ filePaths: string[];
17
+ branch?: string;
18
+ isUpsert?: boolean;
19
+ }
20
+
21
+ export interface PrepareFileuploadOKResponse {
22
+ filePath: string;
23
+ signedUploadUrl: string;
24
+ }
25
+
26
+ export interface NotFoundProblemResponse {
27
+ status: 404;
28
+ title: 'Not Found';
29
+ code: 'ORGANIZATION_NOT_FOUND' | 'API_VERSION_NOT_FOUND';
30
+ }
31
+ }
@@ -0,0 +1,106 @@
1
+ import fetch, { RequestInit } from 'node-fetch';
2
+ import { RegistryApiTypes } from './registry-api-types';
3
+ const version = require('../../package.json').version;
4
+
5
+ export class RegistryApi {
6
+ private readonly baseUrl = `https://api.${process.env.REDOCLY_DOMAIN || 'redoc.ly'}/registry`;
7
+
8
+ constructor(private accessToken?: string) {}
9
+
10
+ private async request(path = '', options: RequestInit = {}) {
11
+ if (!this.accessToken) {
12
+ throw new Error('Unauthorized');
13
+ }
14
+
15
+ const headers = Object.assign({}, options.headers || {}, {
16
+ authorization: this.accessToken,
17
+ 'x-redocly-cli-version': version,
18
+ });
19
+
20
+ const response = await fetch(`${this.baseUrl}${path}`, Object.assign({}, options, { headers }));
21
+
22
+ if (response.status === 401) {
23
+ throw new Error('Unauthorized');
24
+ }
25
+
26
+ if (response.status === 404) {
27
+ const body: RegistryApiTypes.NotFoundProblemResponse = await response.json();
28
+ throw new Error(body.code);
29
+ }
30
+
31
+ return response;
32
+ }
33
+
34
+ setAccessToken(accessToken: string) {
35
+ this.accessToken = accessToken;
36
+ return this;
37
+ }
38
+
39
+ async authStatus(verbose = false) {
40
+ try {
41
+ const response = await this.request();
42
+
43
+ return response.ok;
44
+ } catch (error) {
45
+ if (verbose) {
46
+ console.log(error);
47
+ }
48
+ return false;
49
+ }
50
+ }
51
+
52
+ async prepareFileUpload({
53
+ organizationId,
54
+ name,
55
+ version,
56
+ filesHash,
57
+ filename,
58
+ isUpsert,
59
+ }: RegistryApiTypes.PrepareFileuploadParams): Promise<RegistryApiTypes.PrepareFileuploadOKResponse> {
60
+ const response = await this.request(
61
+ `/${organizationId}/${name}/${version}/prepare-file-upload`,
62
+ {
63
+ method: 'POST',
64
+ headers: { 'content-type': 'application/json' },
65
+ body: JSON.stringify({
66
+ filesHash,
67
+ filename,
68
+ isUpsert,
69
+ }),
70
+ },
71
+ );
72
+
73
+ if (response.ok) {
74
+ return response.json();
75
+ }
76
+
77
+ throw new Error('Could not prepare file upload');
78
+ }
79
+
80
+ async pushApi({
81
+ organizationId,
82
+ name,
83
+ version,
84
+ rootFilePath,
85
+ filePaths,
86
+ branch,
87
+ isUpsert,
88
+ }: RegistryApiTypes.PushApiParams) {
89
+ const response = await this.request(`/${organizationId}/${name}/${version}`, {
90
+ method: 'PUT',
91
+ headers: { 'content-type': 'application/json' },
92
+ body: JSON.stringify({
93
+ rootFilePath,
94
+ filePaths,
95
+ branch,
96
+ isUpsert,
97
+ }),
98
+ });
99
+
100
+ if (response.ok) {
101
+ return;
102
+ }
103
+
104
+ throw new Error('Could not push api');
105
+ }
106
+ }
@@ -1,24 +1,22 @@
1
- import { RedoclyClient } from '../../redocly';
1
+ import { UserContext } from '../../walk';
2
+ import { isRedoclyRegistryURL } from '../../redocly';
2
3
 
3
4
  import { Oas3Decorator, Oas2Decorator } from '../../visitors';
4
5
 
5
6
  export const RegistryDependencies: Oas3Decorator | Oas2Decorator = () => {
6
- let redoclyClient: RedoclyClient;
7
7
  let registryDependencies = new Set<string>();
8
8
 
9
9
  return {
10
10
  DefinitionRoot: {
11
- leave() {
12
- redoclyClient = new RedoclyClient();
13
- if (process.env.UPDATE_REGISTRY && redoclyClient.hasToken()) {
14
- redoclyClient.updateDependencies(Array.from(registryDependencies.keys()));
15
- }
11
+ leave(_: any, ctx: UserContext) {
12
+ const data = ctx.getVisitorData();
13
+ data.links = Array.from(registryDependencies);
16
14
  },
17
15
  },
18
16
  ref(node) {
19
17
  if (node.$ref) {
20
18
  const link = node.$ref.split('#/')[0];
21
- if (RedoclyClient.isRegistryURL(link)) {
19
+ if (isRedoclyRegistryURL(link)) {
22
20
  registryDependencies.add(link);
23
21
  }
24
22
  }
package/src/walk.ts CHANGED
@@ -33,6 +33,7 @@ export type UserContext = {
33
33
  key: string | number;
34
34
  parent: any;
35
35
  oasVersion: OasVersion;
36
+ getVisitorData: () => Record<string, unknown>;
36
37
  };
37
38
 
38
39
  export type Loc = {
@@ -77,6 +78,7 @@ export type NormalizedProblem = {
77
78
  export type WalkContext = {
78
79
  problems: NormalizedProblem[];
79
80
  oasVersion: OasVersion;
81
+ visitorsData: Record<string, Record<string, unknown>>; // custom data store that visitors can use for various purposes
80
82
  refTypes?: Map<string, NormalizedNodeType>;
81
83
  };
82
84
 
@@ -141,6 +143,7 @@ export function walkDocument<T>(opts: {
141
143
  key,
142
144
  parentLocations: {},
143
145
  oasVersion: ctx.oasVersion,
146
+ getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
144
147
  },
145
148
  { node: resolvedNode, location: resolvedLocation, error },
146
149
  );
@@ -325,6 +328,7 @@ export function walkDocument<T>(opts: {
325
328
  key,
326
329
  parentLocations: {},
327
330
  oasVersion: ctx.oasVersion,
331
+ getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
328
332
  },
329
333
  { node: resolvedNode, location: resolvedLocation, error },
330
334
  );
@@ -357,6 +361,7 @@ export function walkDocument<T>(opts: {
357
361
  ignoreNextVisitorsOnNode: () => {
358
362
  ignoreNextVisitorsOnNode = true;
359
363
  },
364
+ getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
360
365
  },
361
366
  collectParents(context),
362
367
  context,
@@ -408,5 +413,10 @@ export function walkDocument<T>(opts: {
408
413
  }),
409
414
  });
410
415
  }
416
+
417
+ function getVisitorDataFn(ruleId: string) {
418
+ ctx.visitorsData[ruleId] = ctx.visitorsData[ruleId] || {};
419
+ return ctx.visitorsData[ruleId];
420
+ }
411
421
  }
412
422
  }