Skip to content

Commit eb7ca1e

Browse files
authored
chore: update getReuniteUrl to take data from scorecard.fromProjectUrl (#2304)
1 parent 8027268 commit eb7ca1e

File tree

16 files changed

+107
-75
lines changed

16 files changed

+107
-75
lines changed

.changeset/ninety-sites-invent.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@redocly/openapi-core": patch
3+
---
4+
5+
Updated @redocly/config to v0.31.0.

.changeset/ninety-spiders-chew.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@redocly/cli": patch
3+
---
4+
5+
Updated authentication logic to get the residency from `scorecard.fromProjectUrl`.

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/src/auth/oauth-client.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,31 @@ import { Buffer } from 'node:buffer';
66
import { logger } from '@redocly/openapi-core';
77
import { type Credentials, RedoclyOAuthDeviceFlow } from './device-flow.js';
88

9-
const SALT = '4618dbc9-8aed-4e27-aaf0-225f4603e5a4';
9+
const CREDENTIAL_SALT = '4618dbc9-8aed-4e27-aaf0-225f4603e5a4';
1010
const CRYPTO_ALGORITHM = 'aes-256-cbc';
1111

1212
export class RedoclyOAuthClient {
13-
public static readonly CREDENTIALS_FILE = 'credentials';
13+
public readonly credentialsFolderPath: string;
14+
public readonly credentialsFilePath: string;
15+
public readonly credentialsFileName: string;
1416

15-
private readonly dir: string;
1617
private readonly key: Buffer;
1718
private readonly iv: Buffer;
1819

1920
constructor() {
2021
const homeDirPath = homedir();
2122

22-
this.dir = path.join(homeDirPath, '.redocly');
23-
mkdirSync(this.dir, { recursive: true });
23+
this.credentialsFolderPath = path.join(homeDirPath, '.redocly');
24+
this.credentialsFileName = 'credentials';
25+
this.credentialsFilePath = path.join(this.credentialsFolderPath, this.credentialsFileName);
2426

25-
this.key = crypto.createHash('sha256').update(`${homeDirPath}${SALT}`).digest(); // 32-byte key
26-
this.iv = crypto.createHash('md5').update(homeDirPath).digest(); // 16-byte IV
27+
this.key = crypto.createHash('sha256').update(`${homeDirPath}${CREDENTIAL_SALT}`).digest();
28+
this.iv = crypto.createHash('md5').update(homeDirPath).digest();
29+
30+
mkdirSync(this.credentialsFolderPath, { recursive: true });
2731
}
2832

29-
public async login(baseUrl: string) {
33+
public async login(baseUrl: string): Promise<void> {
3034
const deviceFlow = new RedoclyOAuthDeviceFlow(baseUrl);
3135

3236
const credentials = await deviceFlow.run();
@@ -79,35 +83,31 @@ export class RedoclyOAuthClient {
7983
}
8084
};
8185

82-
private get credentialsPath() {
83-
return path.join(this.dir, RedoclyOAuthClient.CREDENTIALS_FILE);
84-
}
85-
8686
private async saveCredentials(credentials: Credentials): Promise<void> {
8787
try {
8888
const encryptedCredentials = this.encryptCredentials(credentials);
89-
writeFileSync(this.credentialsPath, encryptedCredentials, 'utf8');
89+
writeFileSync(this.credentialsFilePath, encryptedCredentials, 'utf8');
9090
} catch (error) {
9191
logger.error(`Failed to save credentials: ${error.message}`);
9292
}
9393
}
9494

9595
private async readCredentials(): Promise<Credentials | null> {
96-
if (!existsSync(this.credentialsPath)) {
96+
if (!existsSync(this.credentialsFilePath)) {
9797
return null;
9898
}
9999

100100
try {
101-
const encryptedCredentials = readFileSync(this.credentialsPath, 'utf8');
101+
const encryptedCredentials = readFileSync(this.credentialsFilePath, 'utf8');
102102
return this.decryptCredentials(encryptedCredentials);
103103
} catch {
104104
return null;
105105
}
106106
}
107107

108108
private async removeCredentials(): Promise<void> {
109-
if (existsSync(this.credentialsPath)) {
110-
rmSync(this.credentialsPath);
109+
if (existsSync(this.credentialsFilePath)) {
110+
rmSync(this.credentialsFilePath);
111111
}
112112
}
113113

packages/cli/src/reunite/api/__tests__/domains.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,25 @@ describe('getReuniteUrl()', () => {
4848
expect(getReuniteUrl(config)).toBe('https://app.cloud.eu.redocly.com');
4949
});
5050

51-
it('should use projectUrl from config when no residency provided', async () => {
51+
it('should use fromProjectUrl from config when no residency provided', async () => {
5252
const config = await createConfig({
53-
reunite: { projectUrl: 'https://app.cloud.eu.redocly.com/org/test/project/test' },
53+
scorecard: { fromProjectUrl: 'https://app.cloud.eu.redocly.com/org/test/project/test' },
5454
});
5555
expect(getReuniteUrl(config)).toBe('https://app.cloud.eu.redocly.com');
5656
});
5757

58-
it('should prioritize second parameter over config residency and projectUrl', async () => {
58+
it('should prioritize second parameter over config residency and fromProjectUrl', async () => {
5959
const config = await createConfig({
6060
residency: 'us',
61-
reunite: { projectUrl: 'https://app.cloud.eu.redocly.com/org/test/project/test' },
61+
scorecard: { fromProjectUrl: 'https://app.cloud.eu.redocly.com/org/test/project/test' },
6262
});
6363
expect(getReuniteUrl(config, 'eu')).toBe('https://app.cloud.eu.redocly.com');
6464
});
6565

66-
it('should prioritize residency over projectUrl when both are provided', async () => {
66+
it('should prioritize residency over fromProjectUrl when both are provided', async () => {
6767
const config = await createConfig({
6868
residency: 'us',
69-
reunite: { projectUrl: 'https://app.cloud.eu.redocly.com/org/test/project/test' },
69+
scorecard: { fromProjectUrl: 'https://app.cloud.eu.redocly.com/org/test/project/test' },
7070
});
7171
expect(getReuniteUrl(config)).toBe('https://app.cloud.redocly.com');
7272
});
@@ -76,9 +76,9 @@ describe('getReuniteUrl()', () => {
7676
expect(getReuniteUrl(config, 'https://custom.redocly.com')).toBe('https://custom.redocly.com');
7777
});
7878

79-
it('should handle projectUrl with custom domain and port', async () => {
79+
it('should handle fromProjectUrl with custom domain and port', async () => {
8080
const config = await createConfig({
81-
reunite: { projectUrl: 'https://custom.redocly.com:8080/org/test/project/test' },
81+
scorecard: { fromProjectUrl: 'https://custom.redocly.com:8080/org/test/project/test' },
8282
});
8383
expect(getReuniteUrl(config)).toBe('https://custom.redocly.com:8080');
8484
});
@@ -89,9 +89,9 @@ describe('getReuniteUrl()', () => {
8989
expect(() => getReuniteUrl(testConfig, 'not-a-valid-url')).toThrow('Invalid Reunite URL');
9090
});
9191

92-
it('should throw error for invalid projectUrl', async () => {
92+
it('should throw error for invalid fromProjectUrl', async () => {
9393
const config = await createConfig({
94-
reunite: { projectUrl: 'not-a-valid-url' },
94+
scorecard: { fromProjectUrl: 'not-a-valid-url' },
9595
});
9696
expect(() => getReuniteUrl(config)).toThrow('Invalid Reunite URL');
9797
});

packages/cli/src/reunite/api/domains.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,45 @@ export function getDomain(): string {
99
return process.env.REDOCLY_DOMAIN || REUNITE_URLS.us;
1010
}
1111

12-
export function getReuniteUrl(config: Config | undefined, residencyOption?: string): string {
13-
try {
14-
const residency = residencyOption || config?.resolvedConfig.residency;
15-
16-
if (isLegacyResidency(residency)) {
17-
return REUNITE_URLS[residency];
12+
export const getReuniteUrl = withHttpsValidation(
13+
(config: Config | undefined, residencyOption?: string): string => {
14+
try {
15+
const residency = residencyOption || config?.resolvedConfig.residency;
16+
17+
if (isLegacyResidency(residency)) {
18+
return REUNITE_URLS[residency];
19+
}
20+
21+
if (residency) {
22+
return new URL(residency).origin;
23+
}
24+
25+
if (config?.resolvedConfig.scorecard?.fromProjectUrl) {
26+
return new URL(config.resolvedConfig.scorecard.fromProjectUrl).origin;
27+
}
28+
29+
return REUNITE_URLS.us;
30+
} catch {
31+
throw new InvalidReuniteUrlError();
1832
}
33+
}
34+
);
1935

20-
if (residency) {
21-
return new URL(residency).origin;
22-
}
36+
function withHttpsValidation<Fn extends (...args: any[]) => string>(fn: Fn) {
37+
return (...args: Parameters<Fn>) => {
38+
const url = fn(...args);
2339

24-
if (config?.resolvedConfig.reunite?.projectUrl) {
25-
return new URL(config.resolvedConfig.reunite.projectUrl).origin;
40+
if (!url.startsWith('https://')) {
41+
throw new InvalidReuniteUrlError();
2642
}
2743

28-
return REUNITE_URLS.us;
29-
} catch {
30-
throw new Error('Invalid Reunite URL');
44+
return url;
45+
};
46+
}
47+
48+
export class InvalidReuniteUrlError extends Error {
49+
constructor() {
50+
super('Invalid Reunite URL');
3151
}
3252
}
3353

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
],
5959
"dependencies": {
6060
"@redocly/ajv": "^8.11.2",
61-
"@redocly/config": "^0.30.0",
61+
"@redocly/config": "^0.31.0",
6262
"ajv-formats": "^2.1.1",
6363
"colorette": "^1.2.0",
6464
"js-levenshtein": "^1.1.6",

packages/core/src/bundle.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,13 @@ import { isAbsoluteUrl, isExternalValue, isRef, refBaseName, replaceRef } from '
77
import { initRules } from './config/rules.js';
88
import { reportUnresolvedRef } from './rules/common/no-unresolved-refs.js';
99
import { isTruthy } from './utils.js';
10-
import { dequal } from './dequal.js';
10+
import { dequal } from './utils/dequal.js';
1111
import { RemoveUnusedComponents as RemoveUnusedComponentsOas2 } from './decorators/oas2/remove-unused-components.js';
1212
import { RemoveUnusedComponents as RemoveUnusedComponentsOas3 } from './decorators/oas3/remove-unused-components.js';
1313
import { NormalizedConfigTypes } from './types/redocly-yaml.js';
14-
import { type Config } from './config/index.js';
15-
import {
16-
CONFIG_BUNDLER_VISITOR_ID,
17-
configBundlerVisitor,
18-
pluginsCollectorVisitor,
19-
PLUGINS_COLLECTOR_VISITOR_ID,
20-
} from './config/visitors.js';
14+
import { type Config } from './config/config.js';
15+
import { configBundlerVisitor, pluginsCollectorVisitor } from './config/visitors.js';
16+
import { CONFIG_BUNDLER_VISITOR_ID, PLUGINS_COLLECTOR_VISITOR_ID } from './config/constants.js';
2117

2218
import type { ConfigBundlerVisitorData, PluginsCollectorVisitorData } from './config/visitors.js';
2319
import type { Plugin, ResolvedConfig } from './config/types.js';

packages/core/src/config/config-resolvers.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { colorize, logger } from '../logger.js';
2020
import { asserts, buildAssertCustomFunction } from '../rules/common/assertions/asserts.js';
2121
import { NormalizedConfigTypes } from '../types/redocly-yaml.js';
2222
import { bundleConfig, collectConfigPlugins } from '../bundle.js';
23-
import { CONFIG_FILE_NAME } from './load.js';
23+
import { CONFIG_FILE_NAME, DEFAULT_CONFIG, DEFAULT_PROJECT_PLUGIN_PATHS } from './constants.js';
2424

2525
import type {
2626
Plugin,
@@ -38,8 +38,6 @@ import type {
3838
import type { Asserts, AssertionFn } from '../rules/common/assertions/asserts.js';
3939
import type { Document, ResolvedRefMap } from '../resolve.js';
4040

41-
const DEFAULT_PROJECT_PLUGIN_PATHS = ['@theme/plugin.js', '@theme/plugin.cjs', '@theme/plugin.mjs'];
42-
4341
// Cache instantiated plugins during a single execution
4442
const pluginsCache: Map<string, Plugin[]> = new Map();
4543

@@ -66,8 +64,7 @@ export async function resolveConfig({
6664
resolvedRefMap: ResolvedRefMap;
6765
plugins: Plugin[];
6866
}> {
69-
const config =
70-
rawConfigDocument === undefined ? { extends: ['recommended'] } : rawConfigDocument.parsed;
67+
const config = rawConfigDocument === undefined ? DEFAULT_CONFIG : rawConfigDocument.parsed;
7168

7269
if (customExtends !== undefined && isPlainObject(config)) {
7370
config.extends = customExtends;

packages/core/src/config/config.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getResolveConfig } from './utils.js';
88
import { isAbsoluteUrl } from '../ref-utils.js';
99
import { type Document, type ResolvedRefMap } from '../resolve.js';
1010
import { groupAssertionRules } from './config-resolvers.js';
11+
import { IGNORE_BANNER, IGNORE_FILE } from './constants.js';
1112

1213
import type { NormalizedProblem } from '../walk.js';
1314
import type {
@@ -31,11 +32,6 @@ import type {
3132
RuleSettings,
3233
} from './types.js';
3334

34-
export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
35-
const IGNORE_BANNER =
36-
`# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
37-
`# See https://redocly.com/docs/cli/ for more information.\n`;
38-
3935
function getIgnoreFilePath(configPath?: string): string | undefined {
4036
if (configPath) {
4137
return doesYamlFileExist(configPath)

0 commit comments

Comments
 (0)