diff --git a/.changeset/1887.md b/.changeset/1887.md new file mode 100644 index 00000000..0fed4b55 --- /dev/null +++ b/.changeset/1887.md @@ -0,0 +1,11 @@ +--- +'@asyncapi/cli': patch +--- + +fix: resolve SonarCloud issues in config and validation services + +- 656f398: fix: resolve SonarCloud issues in config and validation services + +Fixes #1881 + + diff --git a/src/apps/cli/commands/config/auth/add.ts b/src/apps/cli/commands/config/auth/add.ts index c6f8685a..fad5298e 100644 --- a/src/apps/cli/commands/config/auth/add.ts +++ b/src/apps/cli/commands/config/auth/add.ts @@ -4,10 +4,10 @@ import { blueBright } from 'picocolors'; import { ConfigService, AuthEntry } from '@/domains/services/config.service'; export default class AuthAdd extends Command { - static description = + static readonly description = 'Add an authentication config for resolving $ref files requiring HTTP Authorization.'; - static args = { + static readonly args = { pattern: Args.string({ required: true, description: @@ -20,7 +20,7 @@ export default class AuthAdd extends Command { }), }; - static flags = { + static readonly flags = { 'auth-type': Flags.string({ char: 'a', description: 'Authentication type (default is "Bearer")', diff --git a/src/domains/services/config.service.ts b/src/domains/services/config.service.ts index b5cf3497..5891579c 100644 --- a/src/domains/services/config.service.ts +++ b/src/domains/services/config.service.ts @@ -1,6 +1,6 @@ -import path from 'path'; -import os from 'os'; -import { promises as fs } from 'fs'; +import path from 'node:path'; +import os from 'node:os'; +import { promises as fs } from 'node:fs'; const CONFIG_DIR = path.join(os.homedir(), '.asyncapi'); const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json'); @@ -51,9 +51,7 @@ export class ConfigService { */ static async addAuthEntry(entry: AuthEntry): Promise { const config = await this.loadConfig(); - if (!config.auth) { - config.auth = []; - } + config.auth ??= []; config.auth.push(entry); await this.saveConfig(config); } @@ -96,11 +94,11 @@ export class ConfigService { * @param pattern - wildcard pattern */ private static wildcardToRegex(pattern: string): RegExp { - const escaped = pattern.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&'); + const escaped = pattern.replaceAll(/[-/\\^$+?.()|[\]{}]/g, String.raw`\$&`); const regexStr = escaped - .replace(/\*\*/g, '.*') - .replace(/\*/g, '[^/]*'); + .replaceAll('**', '.*') + .replaceAll('*', '[^/]*'); // eslint-disable-next-line security/detect-non-literal-regexp return new RegExp(`^${regexStr}`); diff --git a/src/domains/services/validation.service.ts b/src/domains/services/validation.service.ts index a2fcc4e3..e4b833ba 100644 --- a/src/domains/services/validation.service.ts +++ b/src/domains/services/validation.service.ts @@ -57,6 +57,8 @@ const isValidGitHubBlobUrl = (url: string): boolean => { parsedUrl.pathname.split('/')[3] === 'blob' ); } catch (error) { + // This is expected for non-URL strings, just log and return false + console.debug(`Invalid URL format for GitHub blob check: ${url}`); return false; } }; @@ -71,7 +73,7 @@ const convertGitHubWebUrl = (url: string): string => { // Handle GitHub web URLs like: https://github.com/owner/repo/blob/branch/path // eslint-disable-next-line no-useless-escape const githubWebPattern = /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.+)$/; - const match = urlWithoutFragment.match(githubWebPattern); + const match = githubWebPattern.exec(urlWithoutFragment); if (match) { const [, owner, repo, branch, filePath] = match; @@ -81,6 +83,67 @@ const convertGitHubWebUrl = (url: string): string => { return url; }; +/** + * Helper function to fetch URL and handle errors + */ +const fetchWithErrorHandling = async ( + url: string, + headers: Record, + errorMessage: string +): Promise => { + const res = await fetch(url, { headers }); + if (!res.ok) { + throw new Error(`${errorMessage}: ${url} - ${res.statusText}`); + } + return res; +}; + +/** + * Handle GitHub API URLs + */ +const handleGitHubApiUrl = async ( + url: string, + headers: Record +): Promise => { + headers['Accept'] = 'application/vnd.github.v3+json'; + const res = await fetchWithErrorHandling(url, headers, 'Failed to fetch GitHub API URL'); + const fileInfo = (await res.json()) as GitHubFileInfo; + + if (!fileInfo.download_url) { + throw new Error(`No download URL found in GitHub API response for: ${url}`); + } + + const contentRes = await fetchWithErrorHandling( + fileInfo.download_url, + headers, + 'Failed to fetch content from download URL' + ); + return await contentRes.text(); +}; + +/** + * Handle raw GitHub content URLs + */ +const handleRawGitHubUrl = async ( + url: string, + headers: Record +): Promise => { + headers['Accept'] = 'application/vnd.github.v3.raw'; + const res = await fetchWithErrorHandling(url, headers, 'Failed to fetch GitHub URL'); + return await res.text(); +}; + +/** + * Handle regular HTTP/HTTPS URLs + */ +const handleRegularUrl = async ( + url: string, + headers: Record +): Promise => { + const res = await fetchWithErrorHandling(url, headers, 'Failed to fetch URL'); + return await res.text(); +}; + /** * Custom resolver for private repositories */ @@ -108,43 +171,12 @@ const createHttpWithAuthResolver = () => ({ } if (url.includes('api.github.com')) { - headers['Accept'] = 'application/vnd.github.v3+json'; - const res = await fetch(url, { headers }); - if (!res.ok) { - throw new Error( - `Failed to fetch GitHub API URL: ${url} - ${res.statusText}` - ); - } - const fileInfo = (await res.json()) as GitHubFileInfo; - - if (fileInfo.download_url) { - const contentRes = await fetch(fileInfo.download_url, { headers }); - if (!contentRes.ok) { - throw new Error( - `Failed to fetch content from download URL: ${fileInfo.download_url} - ${contentRes.statusText}` - ); - } - return await contentRes.text(); - } - throw new Error( - `No download URL found in GitHub API response for: ${url}` - ); - } else if (url.includes('raw.githubusercontent.com')) { - headers['Accept'] = 'application/vnd.github.v3.raw'; - const res = await fetch(url, { headers }); - if (!res.ok) { - throw new Error( - `Failed to fetch GitHub URL: ${url} - ${res.statusText}` - ); - } - return await res.text(); - } else { - const res = await fetch(url, { headers }); - if (!res.ok) { - throw new Error(`Failed to fetch URL: ${url} - ${res.statusText}`); - } - return await res.text(); + return handleGitHubApiUrl(url, headers); + } + if (url.includes('raw.githubusercontent.com')) { + return handleRawGitHubUrl(url, headers); } + return handleRegularUrl(url, headers); }, });