diff --git a/packages/cli/api-importers/asyncapi-to-ir/package.json b/packages/cli/api-importers/asyncapi-to-ir/package.json index 74a051b105a..3dd559ff1a0 100644 --- a/packages/cli/api-importers/asyncapi-to-ir/package.json +++ b/packages/cli/api-importers/asyncapi-to-ir/package.json @@ -33,6 +33,7 @@ "dependencies": { "@fern-api/ir-sdk": "workspace:*", "@fern-api/ir-utils": "workspace:*", + "@fern-api/task-context": "workspace:*", "@fern-api/v3-importer-commons": "workspace:*", "lodash-es": "catalog:", "openapi-types": "^12.1.3", diff --git a/packages/cli/api-importers/asyncapi-to-ir/src/3.0/channel/ChannelConverter3_0.ts b/packages/cli/api-importers/asyncapi-to-ir/src/3.0/channel/ChannelConverter3_0.ts index 7be9f889f43..dc8775a670b 100644 --- a/packages/cli/api-importers/asyncapi-to-ir/src/3.0/channel/ChannelConverter3_0.ts +++ b/packages/cli/api-importers/asyncapi-to-ir/src/3.0/channel/ChannelConverter3_0.ts @@ -1,5 +1,6 @@ import { HttpHeader, PathParameter, QueryParameter, WebSocketMessage, WebSocketMessageBody } from "@fern-api/ir-sdk"; import { constructHttpPath } from "@fern-api/ir-utils"; +import { CliError } from "@fern-api/task-context"; import { Converters } from "@fern-api/v3-importer-commons"; import { OpenAPIV3 } from "openapi-types"; import { AbstractChannelConverter } from "../../converters/AbstractChannelConverter.js"; @@ -310,7 +311,10 @@ export class ChannelConverter3_0 extends AbstractChannelConverter = {}; @@ -41,7 +42,10 @@ export class FernDefinitionDirectory { } const [directory, ...remainingPath] = pathParts; if (directory == null) { - throw new Error(`Internal error; cannot add file with path: ${pathParts}`); + throw new CliError({ + message: `Internal error; cannot add file with path: ${pathParts}`, + code: CliError.Code.InternalError + }); } if (!this.directories[directory]) { this.directories[directory] = new FernDefinitionDirectory(); diff --git a/packages/cli/api-importers/conjure/conjure-to-fern/package.json b/packages/cli/api-importers/conjure/conjure-to-fern/package.json index 12aa0a6852d..cffff7caa40 100644 --- a/packages/cli/api-importers/conjure/conjure-to-fern/package.json +++ b/packages/cli/api-importers/conjure/conjure-to-fern/package.json @@ -37,6 +37,7 @@ "@fern-api/fern-definition-schema": "workspace:*", "@fern-api/fs-utils": "workspace:*", "@fern-api/importer-commons": "workspace:*", + "@fern-api/task-context": "workspace:*", "js-yaml": "catalog:" }, "devDependencies": { diff --git a/packages/cli/api-importers/conjure/conjure-to-fern/src/ConjureImporter.ts b/packages/cli/api-importers/conjure/conjure-to-fern/src/ConjureImporter.ts index 718e4ef9192..7f0acedeb87 100644 --- a/packages/cli/api-importers/conjure/conjure-to-fern/src/ConjureImporter.ts +++ b/packages/cli/api-importers/conjure/conjure-to-fern/src/ConjureImporter.ts @@ -3,6 +3,7 @@ import { parseEndpointLocator, removeSuffix } from "@fern-api/core-utils"; import { RawSchemas } from "@fern-api/fern-definition-schema"; import { AbsoluteFilePath, dirname, getFilename, join, RelativeFilePath, relativize } from "@fern-api/fs-utils"; import { APIDefinitionImporter, FernDefinitionBuilderImpl, HttpServiceInfo } from "@fern-api/importer-commons"; +import { CliError } from "@fern-api/task-context"; import { listConjureFiles } from "./utils/listConjureFiles.js"; import { visitConjureTypeDeclaration } from "./utils/visitConjureTypeDeclaration.js"; @@ -77,7 +78,10 @@ export class ConjureImporter extends APIDefinitionImporter if (definition.services == null || Object.keys(definition.services ?? {}).length === 0) { const fernFilePath = this.conjureFilepathToFernFilepath[filepath]; if (fernFilePath == null) { - throw new Error(`Failed to find corresponding fern filepath for conjure file ${filepath}`); + throw new CliError({ + message: `Failed to find corresponding fern filepath for conjure file ${filepath}`, + code: CliError.Code.InternalError + }); } for (const [import_, importedFilepath] of Object.entries(definition.types?.conjureImports ?? {})) { @@ -151,9 +155,10 @@ export class ConjureImporter extends APIDefinitionImporter for (const pathParameter of endpointLocator.pathParameters) { const pathParameterType = endpointDeclaration.args[pathParameter]; if (pathParameterType == null) { - throw new Error( - `Failed to find path parameter ${pathParameter} in ${endpointDeclaration.http}` - ); + throw new CliError({ + message: `Failed to find path parameter ${pathParameter} in ${endpointDeclaration.http}`, + code: CliError.Code.InternalError + }); } pathParameters[pathParameter] = typeof pathParameterType === "string" @@ -338,9 +343,10 @@ export class ConjureImporter extends APIDefinitionImporter ); const correspondingFernFilePath = this.conjureFilepathToFernFilepath[relativeFilePathToImportedFile]; if (correspondingFernFilePath == null) { - throw new Error( - `Failed to find corresponding fern filepath for conjure file ${relativeFilePathToImportedFile}` - ); + throw new CliError({ + message: `Failed to find corresponding fern filepath for conjure file ${relativeFilePathToImportedFile}`, + code: CliError.Code.InternalError + }); } return correspondingFernFilePath; } diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildEndpoint.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildEndpoint.ts index 3c39cb7912b..6dba3b578ba 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildEndpoint.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildEndpoint.ts @@ -3,6 +3,7 @@ import { assertNever, MediaType } from "@fern-api/core-utils"; import { RawSchemas } from "@fern-api/fern-definition-schema"; import { Endpoint, EndpointExample, Request, RetriesConfiguration, Schema, SchemaId } from "@fern-api/openapi-ir"; import { RelativeFilePath } from "@fern-api/path-utils"; +import { CliError } from "@fern-api/task-context"; import { buildEndpointExample } from "./buildEndpointExample.js"; import { ERROR_DECLARATIONS_FILENAME, EXTERNAL_AUDIENCE } from "./buildFernDefinition.js"; import { buildHeader } from "./buildHeader.js"; @@ -283,7 +284,10 @@ export function buildEndpoint({ }; }, _other: () => { - throw new Error("Unrecognized Response type: " + endpoint.response?.type); + throw new CliError({ + message: "Unrecognized Response type: " + endpoint.response?.type, + code: CliError.Code.InternalError + }); } }); } diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildWebhooks.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildWebhooks.ts index ceca44ade35..b69772ab07d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildWebhooks.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildWebhooks.ts @@ -13,6 +13,7 @@ import { WebhookTimestampFormat } from "@fern-api/openapi-ir"; import { join, RelativeFilePath } from "@fern-api/path-utils"; +import { CliError } from "@fern-api/task-context"; import { camelCase, isEqual } from "lodash-es"; import { buildHeader } from "./buildHeader.js"; import { buildTypeReference } from "./buildTypeReference.js"; @@ -142,7 +143,10 @@ export function buildWebhooks(context: OpenApiIrConverterContext): void { }; }, _other: () => { - throw new Error("Unrecognized Response type: " + webhook.response?.type); + throw new CliError({ + message: "Unrecognized Response type: " + webhook.response?.type, + code: CliError.Code.InternalError + }); } }); } diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/utils/convertToHttpMethod.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/utils/convertToHttpMethod.ts index 6c2a8977d68..468cdaf246a 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/utils/convertToHttpMethod.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/utils/convertToHttpMethod.ts @@ -1,5 +1,6 @@ import { RawSchemas } from "@fern-api/fern-definition-schema"; import { HttpMethod } from "@fern-api/openapi-ir"; +import { CliError } from "@fern-api/task-context"; export function convertToHttpMethod(httpMethod: HttpMethod): RawSchemas.HttpMethodSchema { return HttpMethod._visit(httpMethod, { @@ -10,13 +11,13 @@ export function convertToHttpMethod(httpMethod: HttpMethod): RawSchemas.HttpMeth delete: () => RawSchemas.HttpMethodSchema.Delete, head: () => RawSchemas.HttpMethodSchema.Head, options: () => { - throw new Error("OPTIONS is unsupported"); + throw new CliError({ message: "OPTIONS is unsupported", code: CliError.Code.ConfigError }); }, trace: () => { - throw new Error("TRACE is unsupported"); + throw new CliError({ message: "TRACE is unsupported", code: CliError.Code.ConfigError }); }, _other: () => { - throw new Error("Unknown http method is unsupported"); + throw new CliError({ message: "Unknown http method is unsupported", code: CliError.Code.ConfigError }); } }); } diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/utils/getEndpointLocation.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/utils/getEndpointLocation.ts index 17b3f307f1f..76be6ce0842 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/utils/getEndpointLocation.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/utils/getEndpointLocation.ts @@ -1,8 +1,8 @@ import { FERN_PACKAGE_MARKER_FILENAME } from "@fern-api/configuration"; import { Endpoint, HttpMethod } from "@fern-api/openapi-ir"; import { join, RelativeFilePath } from "@fern-api/path-utils"; +import { CliError } from "@fern-api/task-context"; import { camelCase, compact, isEqual } from "lodash-es"; - import { convertEndpointSdkNameToFileWithoutExtension } from "./convertSdkGroupName.js"; export interface EndpointLocation { @@ -108,7 +108,10 @@ function getUnresolvedEndpointLocation(endpoint: Endpoint): EndpointLocation { } if (fileParts.length >= operationIdTokens.length) { - throw new Error(`Cannot get file for endpoint ${JSON.stringify(endpoint)}`); + throw new CliError({ + message: `Cannot get file for endpoint ${JSON.stringify(endpoint)}`, + code: CliError.Code.InternalError + }); } return { diff --git a/packages/cli/api-importers/v3-importer-commons/package.json b/packages/cli/api-importers/v3-importer-commons/package.json index 4e9c4b24905..471818b6a8e 100644 --- a/packages/cli/api-importers/v3-importer-commons/package.json +++ b/packages/cli/api-importers/v3-importer-commons/package.json @@ -40,6 +40,7 @@ "@fern-api/ir-sdk": "workspace:*", "@fern-api/ir-utils": "workspace:*", "@fern-api/logger": "workspace:*", + "@fern-api/task-context": "workspace:*", "js-yaml": "catalog:", "js-yaml-source-map": "catalog:", "lodash-es": "catalog:", diff --git a/packages/cli/api-importers/v3-importer-commons/src/utils/CreateTypeReferenceFromFernType.ts b/packages/cli/api-importers/v3-importer-commons/src/utils/CreateTypeReferenceFromFernType.ts index 06a830fddb8..6c15ace87bc 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/utils/CreateTypeReferenceFromFernType.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/utils/CreateTypeReferenceFromFernType.ts @@ -1,5 +1,6 @@ import { recursivelyVisitRawTypeReference } from "@fern-api/fern-definition-schema"; import * as FernIr from "@fern-api/ir-sdk"; +import { CliError } from "@fern-api/task-context"; export function createTypeReferenceFromFernType(fernType: string): FernIr.TypeReference | undefined { return recursivelyVisitRawTypeReference({ @@ -153,7 +154,10 @@ export function createTypeReferenceFromFernType(fernType: string): FernIr.TypeRe string: (value) => FernIr.Literal.string(value), boolean: (value) => FernIr.Literal.boolean(value), _other: () => { - throw new Error("Unexpected literal type"); + throw new CliError({ + message: "Unexpected literal type", + code: CliError.Code.InternalError + }); } }) ) diff --git a/packages/cli/cli/src/cli.ts b/packages/cli/cli/src/cli.ts index 41ff3025a50..c227a7b1540 100644 --- a/packages/cli/cli/src/cli.ts +++ b/packages/cli/cli/src/cli.ts @@ -1869,10 +1869,16 @@ function addDocsPreviewDeleteCommand(cli: Argv, cliContext: Cl .check((argv) => { const sources = [argv.target, argv.url, argv.id].filter(Boolean); if (sources.length === 0) { - throw new Error("Must provide a preview URL or --id."); + throw new CliError({ + message: "Must provide a preview URL or --id.", + code: CliError.Code.ConfigError + }); } if (sources.length > 1) { - throw new Error("Provide only one of: [target], --url, or --id."); + throw new CliError({ + message: "Provide only one of: [target], --url, or --id.", + code: CliError.Code.ConfigError + }); } return true; }), diff --git a/packages/cli/cli/src/commands/docs-preview/deleteDocsPreview.ts b/packages/cli/cli/src/commands/docs-preview/deleteDocsPreview.ts index 40f0ccee156..89141364196 100644 --- a/packages/cli/cli/src/commands/docs-preview/deleteDocsPreview.ts +++ b/packages/cli/cli/src/commands/docs-preview/deleteDocsPreview.ts @@ -45,7 +45,10 @@ function resolveTarget({ return { type: "id", value: id }; } if (target == null) { - throw new Error("Must provide a preview URL or --id."); + throw new CliError({ + message: "Must provide a preview URL or --id.", + code: CliError.Code.ConfigError + }); } if (isPreviewUrl(target)) { return { type: "url", value: target }; diff --git a/packages/cli/cli/src/commands/upgrade/migrations/__test__/loader.test.ts b/packages/cli/cli/src/commands/upgrade/migrations/__test__/loader.test.ts index 3564f171077..c94d7de3bd7 100644 --- a/packages/cli/cli/src/commands/upgrade/migrations/__test__/loader.test.ts +++ b/packages/cli/cli/src/commands/upgrade/migrations/__test__/loader.test.ts @@ -434,6 +434,10 @@ describe("loadAndRunMigrations", () => { ).rejects.toThrow("Invalid generator configuration"); }); + // Timeout bumped to 60s because these two happy-path tests fall through + // to loadMigrationModule, which performs a real `npm install + // @fern-api/generator-migrations@latest`. That call is network-bound and + // regularly exceeds the 5s vitest default on CI (observed 2s–5s+). it("should accept valid config with name property", async () => { const mockLogger = { debug: vi.fn(), @@ -456,7 +460,7 @@ describe("loadAndRunMigrations", () => { expect(mockLogger.warn).not.toHaveBeenCalledWith( expect.stringContaining("Invalid generator configuration structure") ); - }); + }, 60000); it("should accept config with additional properties", async () => { const mockLogger = { @@ -483,6 +487,6 @@ describe("loadAndRunMigrations", () => { expect(mockLogger.warn).not.toHaveBeenCalledWith( expect.stringContaining("Invalid generator configuration structure") ); - }); + }, 60000); }); }); diff --git a/packages/cli/docs-importers/commons/src/FernDocsBuilderImpl.ts b/packages/cli/docs-importers/commons/src/FernDocsBuilderImpl.ts index 94458f11452..1a3943cbeaa 100644 --- a/packages/cli/docs-importers/commons/src/FernDocsBuilderImpl.ts +++ b/packages/cli/docs-importers/commons/src/FernDocsBuilderImpl.ts @@ -7,9 +7,9 @@ import { } from "@fern-api/configuration"; import { FdrAPI as CjsFdrSdk } from "@fern-api/fdr-sdk"; import { AbsoluteFilePath, dirname, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { CliError } from "@fern-api/task-context"; import { cp, mkdir, writeFile } from "fs/promises"; import yaml from "js-yaml"; - import { FernDocsBuilder, FernDocsNavigationBuilder } from "./FernDocsBuilder.js"; interface MarkdownPage { @@ -88,7 +88,7 @@ export class FernDocsBuilderImpl extends FernDocsBuilder { versionConfig: docsYml.RawSchemas.VersionConfig; navigation: docsYml.RawSchemas.VersionFileConfig; }): void { - throw new Error("Method not implemented."); + throw new CliError({ message: "Method not implemented.", code: CliError.Code.InternalError }); } public addNavbarLink({ link }: { link: docsYml.RawSchemas.NavbarLink }): void { diff --git a/packages/cli/docs-importers/mintlify/src/MintlifyImporter.ts b/packages/cli/docs-importers/mintlify/src/MintlifyImporter.ts index 7a2dd4f0dd7..8de427e0fb4 100644 --- a/packages/cli/docs-importers/mintlify/src/MintlifyImporter.ts +++ b/packages/cli/docs-importers/mintlify/src/MintlifyImporter.ts @@ -7,6 +7,7 @@ import { TabInfo } from "@fern-api/docs-importer-commons"; import { AbsoluteFilePath, dirname, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { CliError } from "@fern-api/task-context"; import { readFile } from "fs/promises"; import { convertColors } from "./convertColors.js"; @@ -154,7 +155,11 @@ export class MintlifyImporter extends DocsImporter { if (Object.keys(this.tabUrlToInfo).length > 0) { const tabUrl = getTabForMintItem({ mintItem }); if (tabUrl == null) { - return this.context.failAndThrow(`Failed to assign navigation item to a tab group: ${mintItem.group}`); + return this.context.failAndThrow( + `Failed to assign navigation item to a tab group: ${mintItem.group}`, + undefined, + { code: CliError.Code.ConfigError } + ); } const tab = this.tabUrlToInfo[tabUrl] ?? this.getDefaultDocumentationTab(builder); return tab.navigationBuilder; diff --git a/packages/cli/docs-importers/readme/src/assert.ts b/packages/cli/docs-importers/readme/src/assert.ts index 10818108a59..1aa6c00276a 100644 --- a/packages/cli/docs-importers/readme/src/assert.ts +++ b/packages/cli/docs-importers/readme/src/assert.ts @@ -1,3 +1,4 @@ +import { CliError } from "@fern-api/task-context"; import type { Element, Root } from "hast"; import { CONTINUE, EXIT, visit } from "unist-util-visit"; import { z } from "zod"; @@ -10,7 +11,7 @@ export function assertIsNumber(val: unknown): asserts val is number { export function assertIsDefined(val: T): asserts val is NonNullable { if (val === undefined || val == null) { - throw new Error("Value is nullable."); + throw new CliError({ message: "Value is nullable.", code: CliError.Code.InternalError }); } } diff --git a/packages/cli/docs-importers/readme/src/utils/files/file.ts b/packages/cli/docs-importers/readme/src/utils/files/file.ts index 38bdfa7ce7c..ab45f9e2c6e 100644 --- a/packages/cli/docs-importers/readme/src/utils/files/file.ts +++ b/packages/cli/docs-importers/readme/src/utils/files/file.ts @@ -1,5 +1,6 @@ import { mkdirSync, writeFileSync } from "node:fs"; import { dirname } from "node:path"; +import { CliError } from "@fern-api/task-context"; import { join } from "path"; export function createFilename( @@ -73,7 +74,10 @@ export function writePage({ }) ); } catch (error) { - throw new Error(`${cleanedWritePath}: failed to download to disk`); + throw new CliError({ + message: `${cleanedWritePath}: failed to download to disk`, + code: CliError.Code.NetworkError + }); } } diff --git a/packages/cli/docs-importers/readme/src/utils/files/images.ts b/packages/cli/docs-importers/readme/src/utils/files/images.ts index 1785e130e0b..ef95f6bc657 100644 --- a/packages/cli/docs-importers/readme/src/utils/files/images.ts +++ b/packages/cli/docs-importers/readme/src/utils/files/images.ts @@ -1,6 +1,6 @@ import { existsSync, mkdirSync } from "node:fs"; import { dirname, join } from "node:path"; - +import { CliError } from "@fern-api/task-context"; import type { Result } from "../../types/result.js"; import { fetchImage } from "../network.js"; import { write } from "./file.js"; @@ -42,7 +42,10 @@ async function writeImageToFile(src: string, rootPath: string): Promise const imagePath = join(rootPath, filename); if (!isValidImageSrc(filename)) { - throw new Error(`${filename} - file extension not supported`); + throw new CliError({ + message: `${filename} - file extension not supported`, + code: CliError.Code.InternalError + }); } if (existsSync(imagePath)) { return imagePath; @@ -51,7 +54,7 @@ async function writeImageToFile(src: string, rootPath: string): Promise try { mkdirSync(dirname(imagePath), { recursive: true }); } catch (error) { - throw new Error(`${imagePath} - failed to create directory`); + throw new CliError({ message: `${imagePath} - failed to create directory`, code: CliError.Code.InternalError }); } try { @@ -59,7 +62,10 @@ async function writeImageToFile(src: string, rootPath: string): Promise write(imagePath, imageData); return imagePath; } catch (error) { - throw new Error(`${imagePath}: failed to download file from source`); + throw new CliError({ + message: `${imagePath}: failed to download file from source`, + code: CliError.Code.NetworkError + }); } } diff --git a/packages/cli/docs-importers/readme/src/utils/network.ts b/packages/cli/docs-importers/readme/src/utils/network.ts index 70c8384944e..9eed54d4aab 100644 --- a/packages/cli/docs-importers/readme/src/utils/network.ts +++ b/packages/cli/docs-importers/readme/src/utils/network.ts @@ -1,3 +1,4 @@ +import { CliError } from "@fern-api/task-context"; import { Browser, launch } from "puppeteer"; const userAgent = @@ -34,7 +35,7 @@ export async function startPuppeteer(): Promise { try { return await launch({ headless: true, args: ["--ignore-certificate-errors"] }); } catch (error) { - throw new Error("Could not create a Puppeteer instance"); + throw new CliError({ message: "Could not create a Puppeteer instance", code: CliError.Code.EnvironmentError }); } } @@ -63,7 +64,7 @@ export async function getHtmlWithPuppeteer(browser: Browser, url: string | URL): await page.close(); return content; } catch (error) { - throw new Error("Failed to download page from Puppeteer"); + throw new CliError({ message: "Failed to download page from Puppeteer", code: CliError.Code.NetworkError }); } } @@ -71,11 +72,11 @@ async function fetchPageResponse(url: string | URL): Promise { try { const res = await fetch(url); if (!res.ok) { - throw new Error(`${res.status} ${res.statusText}`); + throw new CliError({ message: `${res.status} ${res.statusText}`, code: CliError.Code.NetworkError }); } return await res.text(); } catch (error) { - throw new Error("Failed to fetch page from source"); + throw new CliError({ message: "Failed to fetch page from source", code: CliError.Code.NetworkError }); } } @@ -90,9 +91,12 @@ export async function fetchPageHtml({ url, browser }: { url: string | URL; brows if (res) { return res; } - throw new Error("An unknown error occurred."); + throw new CliError({ message: "An unknown error occurred.", code: CliError.Code.NetworkError }); } catch (error) { - throw new Error(`Error retrieving HTML for ${url.toString()}`); + throw new CliError({ + message: `Error retrieving HTML for ${url.toString()}`, + code: CliError.Code.NetworkError + }); } } @@ -100,7 +104,7 @@ export async function fetchImage(url: string): Promise { try { const res = await exponentialBackoff(() => fetch(url)); if (!res.ok) { - throw new Error(`${res.status} ${res.statusText}`); + throw new CliError({ message: `${res.status} ${res.statusText}`, code: CliError.Code.NetworkError }); } const imageBuffer = await res.arrayBuffer(); @@ -108,6 +112,9 @@ export async function fetchImage(url: string): Promise { return imageData; } catch (error) { - throw new Error(`Failed to retrieve image from source url ${url}`); + throw new CliError({ + message: `Failed to retrieve image from source url ${url}`, + code: CliError.Code.NetworkError + }); } } diff --git a/packages/cli/docs-preview/src/downloadLocalDocsBundle.ts b/packages/cli/docs-preview/src/downloadLocalDocsBundle.ts index ff3d6dfdb65..410612a773b 100644 --- a/packages/cli/docs-preview/src/downloadLocalDocsBundle.ts +++ b/packages/cli/docs-preview/src/downloadLocalDocsBundle.ts @@ -1,6 +1,7 @@ import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; import { Logger } from "@fern-api/logger"; import { loggingExeca } from "@fern-api/logging-execa"; +import { CliError } from "@fern-api/task-context"; import chalk from "chalk"; import { execSync } from "child_process"; import cliProgress from "cli-progress"; @@ -305,7 +306,10 @@ export async function downloadBundle({ try { const docsBundleZipResponse = await fetch(docsBundleUrl); if (!docsBundleZipResponse.ok) { - throw new Error(`Failed to download docs preview bundle. Status code: ${docsBundleZipResponse.status}`); + throw new CliError({ + message: `Failed to download docs preview bundle. Status code: ${docsBundleZipResponse.status}`, + code: CliError.Code.NetworkError + }); } const outputZipPath = join( absoluteDirectoryToTmpDir, @@ -482,9 +486,11 @@ export async function downloadBundle({ doNotPipeOutput: true }); } catch (error) { - throw new Error( - "Requires [pnpm] to run local development. Please run: npm install -g pnpm, and then: fern docs dev" - ); + throw new CliError({ + message: + "Requires [pnpm] to run local development. Please run: npm install -g pnpm, and then: fern docs dev", + code: CliError.Code.EnvironmentError + }); } try { diff --git a/packages/cli/docs-preview/src/previewDocs.ts b/packages/cli/docs-preview/src/previewDocs.ts index dd980fa0448..6d588ddff8c 100644 --- a/packages/cli/docs-preview/src/previewDocs.ts +++ b/packages/cli/docs-preview/src/previewDocs.ts @@ -26,7 +26,8 @@ import { AbsoluteFilePath, convertToFernHostAbsoluteFilePath, doesPathExist, rel import { IntermediateRepresentation } from "@fern-api/ir-sdk"; import { Project } from "@fern-api/project-loader"; import { convertIrToFdrApi } from "@fern-api/register"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; + import { readFile } from "fs/promises"; import grayMatter from "gray-matter"; import { v4 as uuidv4 } from "uuid"; @@ -128,7 +129,7 @@ export async function getPreviewDocsDefinition({ const docsWorkspace = project.docsWorkspaces; const apiWorkspaces = project.apiWorkspaces; if (docsWorkspace == null) { - throw new Error("No docs workspace found in project"); + throw new CliError({ message: "No docs workspace found in project", code: CliError.Code.ConfigError }); } if (editedAbsoluteFilepaths != null && previousDocsDefinition != null) { diff --git a/packages/cli/docs-preview/src/previewUrlUtils.ts b/packages/cli/docs-preview/src/previewUrlUtils.ts index dbd54a0f2e2..7487b105402 100644 --- a/packages/cli/docs-preview/src/previewUrlUtils.ts +++ b/packages/cli/docs-preview/src/previewUrlUtils.ts @@ -1,3 +1,5 @@ +import { CliError } from "@fern-api/task-context"; + /** * Preview URLs follow the pattern: {org}-preview-{id}.docs.buildwithfern.com */ @@ -63,7 +65,10 @@ export function buildPreviewDomain({ orgId, previewId }: { orgId: string; previe const minIdLength = 8; if (availableSpace < minIdLength) { - throw new Error(`Organization name "${orgId}" is too long to generate a valid preview URL`); + throw new CliError({ + message: `Organization name "${orgId}" is too long to generate a valid preview URL`, + code: CliError.Code.ValidationError + }); } const truncatedId = sanitizedId.slice(0, availableSpace).replace(/-+$/, ""); diff --git a/packages/cli/docs-preview/src/runAppPreviewServer.ts b/packages/cli/docs-preview/src/runAppPreviewServer.ts index 3fed8741628..014bc5bd7c1 100644 --- a/packages/cli/docs-preview/src/runAppPreviewServer.ts +++ b/packages/cli/docs-preview/src/runAppPreviewServer.ts @@ -4,7 +4,8 @@ import { DocsV1Read, DocsV2Read, FernNavigation } from "@fern-api/fdr-sdk"; import { AbsoluteFilePath, dirname, doesPathExist, listFiles, RelativeFilePath, resolve } from "@fern-api/fs-utils"; import { runExeca } from "@fern-api/logging-execa"; import { Project } from "@fern-api/project-loader"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; + import chalk from "chalk"; import { execSync } from "child_process"; import cors from "cors"; @@ -495,9 +496,10 @@ export async function runAppPreviewServer({ try { const url = process.env.APP_DOCS_TAR_PREVIEW_BUCKET; if (url == null) { - throw new Error( - "Failed to connect to the docs preview server. Please contact support@buildwithfern.com" - ); + throw new CliError({ + message: "Failed to connect to the docs preview server. Please contact support@buildwithfern.com", + code: CliError.Code.InternalError + }); } await downloadBundle({ bucketUrl: url, @@ -516,9 +518,11 @@ export async function runAppPreviewServer({ try { const url = process.env.APP_DOCS_PREVIEW_BUCKET; if (url == null) { - throw new Error( - "Failed to connect to the docs preview server. Please contact support@buildwithfern.com" - ); + throw new CliError({ + message: + "Failed to connect to the docs preview server. Please contact support@buildwithfern.com", + code: CliError.Code.InternalError + }); } await downloadBundle({ bucketUrl: url, diff --git a/packages/cli/docs-preview/src/runPreviewServer.ts b/packages/cli/docs-preview/src/runPreviewServer.ts index 427239928f8..f8d036701f2 100644 --- a/packages/cli/docs-preview/src/runPreviewServer.ts +++ b/packages/cli/docs-preview/src/runPreviewServer.ts @@ -2,7 +2,8 @@ import { wrapWithHttps } from "@fern-api/docs-resolver"; import { DocsV1Read, DocsV2Read, FernNavigation } from "@fern-api/fdr-sdk"; import { AbsoluteFilePath, dirname, doesPathExist } from "@fern-api/fs-utils"; import { Project } from "@fern-api/project-loader"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; + import chalk from "chalk"; import cors from "cors"; import express from "express"; @@ -77,9 +78,10 @@ export async function runPreviewServer({ try { const url = process.env.DOCS_PREVIEW_BUCKET; if (url == null) { - throw new Error( - "Failed to connect to the docs preview server. Please contact support@buildwithfern.com" - ); + throw new CliError({ + message: "Failed to connect to the docs preview server. Please contact support@buildwithfern.com", + code: CliError.Code.InternalError + }); } await downloadBundle({ bucketUrl: url, logger: context.logger, preferCached: true, tryTar: false }); } catch (err) { diff --git a/packages/cli/docs-resolver/src/ApiReferenceNodeConverter.ts b/packages/cli/docs-resolver/src/ApiReferenceNodeConverter.ts index 520ff98454d..c0d7111c556 100644 --- a/packages/cli/docs-resolver/src/ApiReferenceNodeConverter.ts +++ b/packages/cli/docs-resolver/src/ApiReferenceNodeConverter.ts @@ -2,7 +2,7 @@ import { docsYml } from "@fern-api/configuration-loader"; import { isNonNullish, titleCase } from "@fern-api/core-utils"; import { APIV1Read, FdrAPI, FernNavigation } from "@fern-api/fdr-sdk"; import { AbsoluteFilePath } from "@fern-api/fs-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import { visitDiscriminatedUnion } from "@fern-api/ui-core-utils"; import { DocsWorkspace, FernWorkspace } from "@fern-api/workspace-loader"; import { camelCase, kebabCase } from "lodash-es"; @@ -1356,7 +1356,9 @@ export class ApiReferenceNodeConverter { private getFileId(filepath: AbsoluteFilePath): string { const fileId = this.collectedFileIds.get(filepath); if (fileId == null) { - return this.taskContext.failAndThrow("Failed to locate file after uploading: " + filepath); + return this.taskContext.failAndThrow("Failed to locate file after uploading: " + filepath, undefined, { + code: CliError.Code.InternalError + }); } return fileId; } diff --git a/packages/cli/docs-resolver/src/DocsDefinitionResolver.ts b/packages/cli/docs-resolver/src/DocsDefinitionResolver.ts index e3ade737042..8b31b33d4aa 100644 --- a/packages/cli/docs-resolver/src/DocsDefinitionResolver.ts +++ b/packages/cli/docs-resolver/src/DocsDefinitionResolver.ts @@ -25,7 +25,8 @@ import { generateIntermediateRepresentation } from "@fern-api/ir-generator"; import { IntermediateRepresentation } from "@fern-api/ir-sdk"; import { getSnakeCaseUnsafe } from "@fern-api/ir-utils"; import { OSSWorkspace } from "@fern-api/lazy-fern-workspace"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; + import { AbstractAPIWorkspace, DocsWorkspace, FernWorkspace } from "@fern-api/workspace-loader"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; @@ -246,7 +247,7 @@ export class DocsDefinitionResolver { private _parsedDocsConfig: WithoutQuestionMarks | undefined; private get parsedDocsConfig(): WithoutQuestionMarks { if (this._parsedDocsConfig == null) { - throw new Error("parsedDocsConfig is not set"); + throw new CliError({ message: "parsedDocsConfig is not set", code: CliError.Code.InternalError }); } return this._parsedDocsConfig; } @@ -912,7 +913,7 @@ export class DocsDefinitionResolver { const errorMessage = apiSection.apiName ? `Failed to load API Definition '${apiSection.apiName}' referenced in docs.\nA valid API configuration was not found at the path: fern/apis/${apiSection.apiName}.\nLearn more about project structure:\nhttps://buildwithfern.com/learn/docs/getting-started/project-structure#api-definitions` : "Failed to load API Definition referenced in docs.\nLearn more about project structure:\nhttps://buildwithfern.com/learn/docs/getting-started/project-structure#api-definitions"; - throw new Error(errorMessage); + throw new CliError({ message: errorMessage, code: CliError.Code.ConfigError }); } private getOpenApiWorkspaceForApiSection(apiSection: docsYml.DocsNavigationItem.ApiSection): OSSWorkspace { @@ -927,7 +928,7 @@ export class DocsDefinitionResolver { const errorMessage = apiSection.apiName ? `Failed to load API Definition '${apiSection.apiName}' referenced in docs.\nA valid API configuration was not found at the path: fern/apis/${apiSection.apiName}.\nLearn more about project structure:\nhttps://buildwithfern.com/learn/docs/getting-started/project-structure#api-definitions` : "Failed to load API Definition referenced in docs.\nLearn more about project structure:\nhttps://buildwithfern.com/learn/docs/getting-started/project-structure#api-definitions"; - throw new Error(errorMessage); + throw new CliError({ message: errorMessage, code: CliError.Code.ConfigError }); } private async toRootNode(): Promise { @@ -1035,9 +1036,10 @@ export class DocsDefinitionResolver { const defaultVersion = versioned.versions[0]; if (defaultVersion?.hidden === true) { - throw new Error( - `The default version "${defaultVersion.version}" cannot be hidden. Please set a non-hidden version as the first version in your versions list, or remove the hidden flag from the default version.` - ); + throw new CliError({ + message: `The default version "${defaultVersion.version}" cannot be hidden. Please set a non-hidden version as the first version in your versions list, or remove the hidden flag from the default version.`, + code: CliError.Code.ConfigError + }); } // Process versions in batches to reduce memory usage for customers with many API versions. @@ -1469,7 +1471,11 @@ export class DocsDefinitionResolver { ir, { onError: (e) => - this.taskContext.failAndThrow(`Error substituting environment variables in API spec: ${e}`) + this.taskContext.failAndThrow( + `Error substituting environment variables in API spec: ${e}`, + undefined, + { code: CliError.Code.EnvironmentError } + ) }, { substituteAsEmpty: false } ); @@ -2096,7 +2102,9 @@ export class DocsDefinitionResolver { } const fileId = this.collectedFileIds.get(filepath); if (fileId == null) { - return this.taskContext.failAndThrow("Failed to locate file after uploading: " + filepath); + return this.taskContext.failAndThrow("Failed to locate file after uploading: " + filepath, undefined, { + code: CliError.Code.InternalError + }); } return DocsV1Write.FileId(fileId); } @@ -2282,7 +2290,10 @@ export class DocsDefinitionResolver { value: this.getFileId(value) }), url: ({ value }) => ({ type: "url", value: DocsV1Write.Url(value) }), - _other: () => this.taskContext.failAndThrow("Invalid metadata configuration") + _other: () => + this.taskContext.failAndThrow("Invalid metadata configuration", undefined, { + code: CliError.Code.ConfigError + }) }); } diff --git a/packages/cli/docs-resolver/src/NodeIdGenerator.ts b/packages/cli/docs-resolver/src/NodeIdGenerator.ts index 27c5867a907..4751a1fcee9 100644 --- a/packages/cli/docs-resolver/src/NodeIdGenerator.ts +++ b/packages/cli/docs-resolver/src/NodeIdGenerator.ts @@ -1,4 +1,5 @@ import { FernNavigation } from "@fern-api/fdr-sdk"; +import { CliError } from "@fern-api/task-context"; import crypto from "crypto"; function hash(id: string): string { @@ -28,7 +29,10 @@ export class NodeIdGenerator { hashedId = this.#getHashedId(id); loop++; if (loop > 100) { - throw new Error(`Infinite loop detected for id: ${id}`); + throw new CliError({ + message: `Infinite loop detected for id: ${id}`, + code: CliError.Code.ReferenceError + }); } } return hashedId; diff --git a/packages/cli/docs-resolver/src/utils/mergeEndpointPairs.ts b/packages/cli/docs-resolver/src/utils/mergeEndpointPairs.ts index 61b22876e8f..263508596bc 100644 --- a/packages/cli/docs-resolver/src/utils/mergeEndpointPairs.ts +++ b/packages/cli/docs-resolver/src/utils/mergeEndpointPairs.ts @@ -1,4 +1,5 @@ import { FernNavigation } from "@fern-api/fdr-sdk"; +import { CliError } from "@fern-api/task-context"; export function mergeEndpointPairs({ children, @@ -28,7 +29,10 @@ export function mergeEndpointPairs({ const endpoint = findEndpointById(child.endpointId); if (endpoint == null) { - throw new Error(`Endpoint ${child.endpointId} not found`); + throw new CliError({ + message: `Endpoint ${child.endpointId} not found`, + code: CliError.Code.InternalError + }); } const methodAndPath = `${endpoint.method} ${stringifyEndpointPathParts(endpoint)}`; diff --git a/packages/cli/fern-definition/formatter/src/formatWorkspace.ts b/packages/cli/fern-definition/formatter/src/formatWorkspace.ts index e7d380c3bc5..bc9df1cb760 100644 --- a/packages/cli/fern-definition/formatter/src/formatWorkspace.ts +++ b/packages/cli/fern-definition/formatter/src/formatWorkspace.ts @@ -1,6 +1,6 @@ import { FernWorkspace } from "@fern-api/api-workspace-commons"; import { entries } from "@fern-api/core-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import chalk from "chalk"; import { writeFile } from "fs/promises"; @@ -25,7 +25,7 @@ export async function formatFernWorkspace({ context.logger.info(chalk.green(`Formatted ${chalk.bold(relativeFilepath)}`)); } else { context.logger.info(chalk.red(`Invalid formatting: ${chalk.bold(relativeFilepath)}`)); - context.failWithoutThrowing(); + context.failWithoutThrowing(undefined, undefined, { code: CliError.Code.ValidationError }); } } } diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/JsonSchemaConverterContext.ts b/packages/cli/fern-definition/ir-to-jsonschema/src/JsonSchemaConverterContext.ts index 6cddea99744..e306342a55c 100644 --- a/packages/cli/fern-definition/ir-to-jsonschema/src/JsonSchemaConverterContext.ts +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/JsonSchemaConverterContext.ts @@ -1,6 +1,6 @@ import { IntermediateRepresentation, TypeDeclaration, TypeId, TypeReference } from "@fern-api/ir-sdk"; import { getOriginalName } from "@fern-api/ir-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import { JSONSchema4 } from "json-schema"; export class JsonSchemaConverterContext { @@ -20,7 +20,7 @@ export class JsonSchemaConverterContext { } else { // context.logger.error(`Type declaration not found for ${typeName}`); } - return this.context.failAndThrow(); + return this.context.failAndThrow(undefined, undefined, { code: CliError.Code.ReferenceError }); } return typeDeclaration; } diff --git a/packages/cli/fern-definition/validator/src/rules/valid-navigation/valid-navigation.ts b/packages/cli/fern-definition/validator/src/rules/valid-navigation/valid-navigation.ts index 68ffcb8a8d8..9bf2f093dc8 100644 --- a/packages/cli/fern-definition/validator/src/rules/valid-navigation/valid-navigation.ts +++ b/packages/cli/fern-definition/validator/src/rules/valid-navigation/valid-navigation.ts @@ -2,6 +2,7 @@ import { getAllDefinitionFiles, getAllNamedDefinitionFiles } from "@fern-api/api import { FERN_PACKAGE_MARKER_FILENAME } from "@fern-api/configuration-loader"; import { keys } from "@fern-api/core-utils"; import { dirname, join, RelativeFilePath, relative } from "@fern-api/fs-utils"; +import { CliError } from "@fern-api/task-context"; import path from "path"; import { Rule, RuleViolation } from "../../Rule.js"; @@ -51,7 +52,10 @@ export const ValidNavigationRule: Rule = { const expectedItems = directoryToChildren[dirname(relativeFilepath)]; if (expectedItems == null) { - throw new Error(`Could not find expected contents of ${relativeFilepath}`); + throw new CliError({ + message: `Could not find expected contents of ${relativeFilepath}`, + code: CliError.Code.InternalError + }); } const violations: RuleViolation[] = []; diff --git a/packages/cli/fern-definition/validator/src/rules/valid-pagination/valid-pagination.ts b/packages/cli/fern-definition/validator/src/rules/valid-pagination/valid-pagination.ts index 7f7c675dd33..f281ba449c7 100644 --- a/packages/cli/fern-definition/validator/src/rules/valid-pagination/valid-pagination.ts +++ b/packages/cli/fern-definition/validator/src/rules/valid-pagination/valid-pagination.ts @@ -5,6 +5,7 @@ import { isRawUriPaginationSchema, TypeResolverImpl } from "@fern-api/ir-generator"; +import { CliError } from "@fern-api/task-context"; import { Rule } from "../../Rule.js"; import { CASINGS_GENERATOR } from "../../utils/casingsGenerator.js"; @@ -77,7 +78,10 @@ export const ValidPaginationRule: Rule = { pathPagination: endpointPagination }); } - throw new Error("Invalid pagination schema"); + throw new CliError({ + message: "Invalid pagination schema", + code: CliError.Code.ValidationError + }); } } }; diff --git a/packages/cli/generation/ir-generator-tests/src/loadApisOrThrow.ts b/packages/cli/generation/ir-generator-tests/src/loadApisOrThrow.ts index 8045f719a99..512b0a9d3b0 100644 --- a/packages/cli/generation/ir-generator-tests/src/loadApisOrThrow.ts +++ b/packages/cli/generation/ir-generator-tests/src/loadApisOrThrow.ts @@ -1,9 +1,13 @@ import { loadApis } from "@fern-api/project-loader"; +import { CliError } from "@fern-api/task-context"; export async function loadApisOrThrow(...args: Parameters): ReturnType { const result = await loadApis(...args); if (result.length === 0) { - throw new Error("No APIs found. Args: " + JSON.stringify(args, null, 2)); + throw new CliError({ + message: "No APIs found. Args: " + JSON.stringify(args, null, 2), + code: CliError.Code.ConfigError + }); } return result; } diff --git a/packages/cli/generation/ir-migrations/src/IntermediateRepresentationMigrator.ts b/packages/cli/generation/ir-migrations/src/IntermediateRepresentationMigrator.ts index 52d3966532b..4355cf6047c 100644 --- a/packages/cli/generation/ir-migrations/src/IntermediateRepresentationMigrator.ts +++ b/packages/cli/generation/ir-migrations/src/IntermediateRepresentationMigrator.ts @@ -1,7 +1,7 @@ import { GeneratorName } from "@fern-api/configuration-loader"; import { IntermediateRepresentation, serialization as IrSerialization } from "@fern-api/ir-sdk"; import { isVersionAhead } from "@fern-api/semver-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import { GENERATOR_MINIMUM_VERSIONS, MINIMUM_SUPPORTED_IR_VERSION } from "./generatorVersionMap.js"; import { GeneratorNameAndVersion } from "./IrMigrationContext.js"; import { V54_TO_V53_MIGRATION } from "./migrations/v54-to-v53/migrateFromV54ToV53.js"; @@ -137,17 +137,21 @@ class IntermediateRepresentationMigratorImpl implements IntermediateRepresentati if (targetGenerator != null) { const minVersion = this.getMinimumGeneratorVersion(targetGenerator.name); if (minVersion != null) { - throw new Error( - `${targetGenerator.name}@${targetGenerator.version} is not compatible with CLI v4.x.x+. ` + - `Please upgrade to ${targetGenerator.name}@${minVersion} or later using 'fern generator upgrade --include-major'.` - ); + throw new CliError({ + message: + `${targetGenerator.name}@${targetGenerator.version} is not compatible with CLI v4.x.x+. ` + + `Please upgrade to ${targetGenerator.name}@${minVersion} or later using 'fern generator upgrade --include-major'.`, + code: CliError.Code.VersionError + }); } } - throw new Error( - "This generator version is not compatible with CLI v4.x.x+. " + - "Please upgrade your generator using 'fern generator upgrade --include-major'." - ); + throw new CliError({ + message: + "This generator version is not compatible with CLI v4.x.x+. " + + "Please upgrade your generator using 'fern generator upgrade --include-major'.", + code: CliError.Code.VersionError + }); } } @@ -191,7 +195,7 @@ class IntermediateRepresentationMigratorImpl implements IntermediateRepresentati // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!hasEncounteredMigrationYet) { - context.failAndThrow(`IR ${version} does not exist`); + context.failAndThrow(`IR ${version} does not exist`, undefined, { code: CliError.Code.VersionError }); } return migrated; @@ -244,21 +248,30 @@ class IntermediateRepresentationMigratorImpl implements IntermediateRepresentati migration.firstGeneratorVersionToConsumeNewIR[targetGenerator.name as GeneratorName]; if (minVersionToExclude == null) { - throw new Error( - `Cannot migrate intermediate representation. Unrecognized generator: ${targetGenerator.name}. If leveraging a custom generator, ensure you are specifying "ir-version" within the generator configuration.` - ); + throw new CliError({ + message: `Cannot migrate intermediate representation. Unrecognized generator: ${targetGenerator.name}. If leveraging a custom generator, ensure you are specifying "ir-version" within the generator configuration.`, + code: CliError.Code.ConfigError + }); } switch (minVersionToExclude) { case GeneratorWasNeverUpdatedToConsumeNewIR: return true; case GeneratorWasNotCreatedYet: - throw new Error( - `Cannot migrate intermediate representation. Generator was created after intermediate representation ${migration.laterVersion}.` - ); + throw new CliError({ + message: `Cannot migrate intermediate representation. Generator was created after intermediate representation ${migration.laterVersion}.`, + code: CliError.Code.VersionError + }); } - return isVersionAhead(minVersionToExclude, targetGenerator.version); + try { + return isVersionAhead(minVersionToExclude, targetGenerator.version); + } catch (error) { + throw new CliError({ + message: `Failed to compare versions: ${error instanceof Error ? error.message : String(error)}`, + code: CliError.Code.VersionError + }); + } } public getIRVersionForGenerator({ diff --git a/packages/cli/generation/ir-migrations/src/migrations/v55-to-v54/migrateFromV55ToV54.ts b/packages/cli/generation/ir-migrations/src/migrations/v55-to-v54/migrateFromV55ToV54.ts index c30af39344a..652782280ce 100644 --- a/packages/cli/generation/ir-migrations/src/migrations/v55-to-v54/migrateFromV55ToV54.ts +++ b/packages/cli/generation/ir-migrations/src/migrations/v55-to-v54/migrateFromV55ToV54.ts @@ -1,7 +1,8 @@ import { GeneratorName } from "@fern-api/configuration-loader"; import { assertNever } from "@fern-api/core-utils"; -import { mapValues } from "lodash-es"; +import { CliError } from "@fern-api/task-context"; +import { mapValues } from "lodash-es"; import { IrSerialization } from "../../ir-serialization/index.js"; import { IrVersions } from "../../ir-versions/index.js"; import { @@ -122,7 +123,7 @@ function convertTypes( ); }, _other: () => { - throw new Error("Encountered unknown shape"); + throw new CliError({ message: "Encountered unknown shape", code: CliError.Code.InternalError }); } }) }; @@ -333,7 +334,10 @@ function convertTypeReference(typeReference: IrVersions.V55.types.TypeReference) named: IrVersions.V54.types.TypeReference.named, unknown: IrVersions.V54.types.TypeReference.unknown, _other: () => { - throw new Error("Unknown type reference: " + typeReference.type); + throw new CliError({ + message: "Unknown type reference: " + typeReference.type, + code: CliError.Code.InternalError + }); } }); } @@ -370,7 +374,10 @@ function convertContainerType(container: IrVersions.V55.types.ContainerType): Ir }), literal: IrVersions.V54.types.ContainerType.literal, _other: () => { - throw new Error("Unknown ContainerType: " + container.type); + throw new CliError({ + message: "Unknown ContainerType: " + container.type, + code: CliError.Code.InternalError + }); } }); } @@ -399,7 +406,10 @@ function convertSingleUnionTypeProperties( }), noProperties: IrVersions.V54.types.SingleUnionTypeProperties.noProperties, _other: () => { - throw new Error(`Unknown SingleUnionTypeProperties: ${JSON.stringify(properties)}`); + throw new CliError({ + message: `Unknown SingleUnionTypeProperties: ${JSON.stringify(properties)}`, + code: CliError.Code.InternalError + }); } } ); @@ -415,7 +425,10 @@ function convertResolvedType( IrVersions.V54.types.ResolvedTypeReference.primitive(convertPrimitiveType(primitiveType)), unknown: IrVersions.V54.types.ResolvedTypeReference.unknown, _other: () => { - throw new Error("Unknown ResolvedTypeReference: " + resolvedType.type); + throw new CliError({ + message: "Unknown ResolvedTypeReference: " + resolvedType.type, + code: CliError.Code.InternalError + }); } }); } @@ -664,7 +677,10 @@ function convertSdkRequestShape(shape: IrVersions.V55.http.SdkRequestShape): IrV IrVersions.V54.http.SdkRequestShape.justRequestBody(convertSdkRequestBodyType(reference)), wrapper: IrVersions.V54.http.SdkRequestShape.wrapper, _other: () => { - throw new Error("Unknown SdkRequestShape: " + shape.type); + throw new CliError({ + message: "Unknown SdkRequestShape: " + shape.type, + code: CliError.Code.InternalError + }); } }); } @@ -723,7 +739,10 @@ function convertRequestBody(requestBody: IrVersions.V55.http.HttpRequestBody): I return IrVersions.V54.http.HttpRequestBody.bytes(bytes); }, _other: () => { - throw new Error("Unknown HttpRequestBody: " + requestBody.type); + throw new CliError({ + message: "Unknown HttpRequestBody: " + requestBody.type, + code: CliError.Code.InternalError + }); } }); } diff --git a/packages/cli/generation/ir-migrations/src/migrations/v63-to-v62/migrateFromV63ToV62.ts b/packages/cli/generation/ir-migrations/src/migrations/v63-to-v62/migrateFromV63ToV62.ts index 73f1d8bda0e..7747cd36c7d 100644 --- a/packages/cli/generation/ir-migrations/src/migrations/v63-to-v62/migrateFromV63ToV62.ts +++ b/packages/cli/generation/ir-migrations/src/migrations/v63-to-v62/migrateFromV63ToV62.ts @@ -1,4 +1,5 @@ import { GeneratorName } from "@fern-api/configuration-loader"; +import { CliError } from "@fern-api/task-context"; import { mapValues } from "lodash-es"; import { IrSerialization } from "../../ir-serialization/index.js"; import { IrVersions } from "../../ir-versions/index.js"; @@ -161,7 +162,7 @@ function convertAuthScheme(scheme: IrVersions.V63.AuthScheme): IrVersions.V62.Au // in the serializer, we can pass it through with a type assertion return IrVersions.V62.AuthScheme.inferred(scheme as unknown as IrVersions.V62.auth.InferredAuthScheme); default: - throw new Error("Unknown AuthScheme type"); + throw new CliError({ message: "Unknown AuthScheme type", code: CliError.Code.InternalError }); } } diff --git a/packages/cli/init/src/__test__/initializeWithMintlify.test.ts b/packages/cli/init/src/__test__/initializeWithMintlify.test.ts index 286dbd6ac76..b29ca8e9725 100644 --- a/packages/cli/init/src/__test__/initializeWithMintlify.test.ts +++ b/packages/cli/init/src/__test__/initializeWithMintlify.test.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { AbsoluteFilePath, cwd, resolve } from "@fern-api/fs-utils"; import { runMintlifyMigration } from "@fern-api/mintlify-importer"; +import { CliError } from "@fern-api/task-context"; import { vi } from "vitest"; import { initializeWithMintlify } from "../initializeWithMintlify.js"; @@ -29,7 +30,9 @@ describe("initializeWithMintlify", () => { ).rejects.toThrow(); expect(taskContext.failAndThrow).toHaveBeenCalledWith( - "Clone the repo locally and run this command again by referencing the path to the local mint.json file" + "Clone the repo locally and run this command again by referencing the path to the local mint.json file", + undefined, + { code: CliError.Code.ConfigError } ); }); @@ -50,7 +53,9 @@ describe("initializeWithMintlify", () => { }) ).rejects.toThrow(); - expect(taskContext.failAndThrow).toHaveBeenCalledWith("Provide a path to a mint.json file"); + expect(taskContext.failAndThrow).toHaveBeenCalledWith("Provide a path to a mint.json file", undefined, { + code: CliError.Code.ConfigError + }); }); it("Throws an error if the mint.json file does not exist", async () => { @@ -72,7 +77,9 @@ describe("initializeWithMintlify", () => { const absolutePathToMintJson = resolve(cwd(), "./mint.json"); - expect(taskContext.failAndThrow).toHaveBeenCalledWith(`${absolutePathToMintJson} does not exist`); + expect(taskContext.failAndThrow).toHaveBeenCalledWith(`${absolutePathToMintJson} does not exist`, undefined, { + code: CliError.Code.ConfigError + }); }); it("Successfully runs the mintlify migration if a proper mint.json file is provided", async () => { diff --git a/packages/cli/init/src/createFernDirectoryAndOrganization.ts b/packages/cli/init/src/createFernDirectoryAndOrganization.ts index 7552c6a5427..8b9a946787b 100644 --- a/packages/cli/init/src/createFernDirectoryAndOrganization.ts +++ b/packages/cli/init/src/createFernDirectoryAndOrganization.ts @@ -8,7 +8,7 @@ import { import { createVenusService } from "@fern-api/core"; import { AbsoluteFilePath, cwd, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; import { askToLogin } from "@fern-api/login"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import chalk from "chalk"; import { mkdir, writeFile } from "fs/promises"; import { kebabCase } from "lodash-es"; @@ -46,7 +46,9 @@ export async function createFernDirectoryAndWorkspace({ if (response.ok) { organization = response.body.organizationId; } else { - taskContext.failAndThrow("Unauthorized. FERN_TOKEN is invalid."); + taskContext.failAndThrow("Unauthorized. FERN_TOKEN is invalid.", undefined, { + code: CliError.Code.AuthError + }); // dummy return value to appease the linter. won't actually ever get run. return { absolutePathToFernDirectory: AbsoluteFilePath.of("/dummy"), organization: "dummy" }; } diff --git a/packages/cli/init/src/createWorkspace.ts b/packages/cli/init/src/createWorkspace.ts index 61609fd311a..4963753b315 100644 --- a/packages/cli/init/src/createWorkspace.ts +++ b/packages/cli/init/src/createWorkspace.ts @@ -10,7 +10,7 @@ import { import { formatDefinitionFile } from "@fern-api/fern-definition-formatter"; import { RootApiFileSchema } from "@fern-api/fern-definition-schema"; import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath, relative } from "@fern-api/fs-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import { mkdir, writeFile } from "fs/promises"; import yaml from "js-yaml"; @@ -185,10 +185,19 @@ async function writeSampleApiDefinition({ await writeFile(join(directoryOfDefinition, RelativeFilePath.of(ROOT_API_FILENAME)), yaml.dump(rootApi)); const absoluteFilepathToImdbYaml = join(directoryOfDefinition, RelativeFilePath.of("imdb.yml")); - await writeFile( - absoluteFilepathToImdbYaml, - await formatDefinitionFile({ + let formattedContent: string; + try { + formattedContent = await formatDefinitionFile({ fileContents: SAMPLE_IMDB_API - }) - ); + }); + } catch (error) { + if (error instanceof CliError) { + throw error; + } + throw new CliError({ + message: `Failed to format definition file: ${error instanceof Error ? error.message : String(error)}`, + code: CliError.Code.ParseError + }); + } + await writeFile(absoluteFilepathToImdbYaml, formattedContent); } diff --git a/packages/cli/init/src/initializeWithMintlify.ts b/packages/cli/init/src/initializeWithMintlify.ts index 4b26507211c..5d41ac8254b 100644 --- a/packages/cli/init/src/initializeWithMintlify.ts +++ b/packages/cli/init/src/initializeWithMintlify.ts @@ -1,6 +1,6 @@ import { AbsoluteFilePath, cwd, doesPathExist, isURL, resolve } from "@fern-api/fs-utils"; import { runMintlifyMigration } from "@fern-api/mintlify-importer"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; export const initializeWithMintlify = async ({ pathToMintJson, @@ -15,7 +15,9 @@ export const initializeWithMintlify = async ({ }): Promise => { // The file path should include `mint.json` in it if (!pathToMintJson?.includes("mint.json")) { - taskContext.failAndThrow("Provide a path to a mint.json file"); + taskContext.failAndThrow("Provide a path to a mint.json file", undefined, { + code: CliError.Code.ConfigError + }); return; } @@ -24,7 +26,9 @@ export const initializeWithMintlify = async ({ // @todo get urls to work - for now, throw an error if the user provides a URL if (isURL(pathToMintJson)) { taskContext.failAndThrow( - "Clone the repo locally and run this command again by referencing the path to the local mint.json file" + "Clone the repo locally and run this command again by referencing the path to the local mint.json file", + undefined, + { code: CliError.Code.ConfigError } ); return; } else { @@ -34,7 +38,9 @@ export const initializeWithMintlify = async ({ const pathExists = await doesPathExist(absolutePathToMintJson); if (!pathExists || !absolutePathToMintJson) { - taskContext.failAndThrow(`${absolutePathToMintJson} does not exist`); + taskContext.failAndThrow(`${absolutePathToMintJson} does not exist`, undefined, { + code: CliError.Code.ConfigError + }); return; } diff --git a/packages/cli/init/src/initializeWithReadme.ts b/packages/cli/init/src/initializeWithReadme.ts index 3e8bb700408..eb71b5fa0e8 100644 --- a/packages/cli/init/src/initializeWithReadme.ts +++ b/packages/cli/init/src/initializeWithReadme.ts @@ -1,6 +1,6 @@ import { AbsoluteFilePath, cwd, isURL } from "@fern-api/fs-utils"; import { runReadmeMigration } from "@fern-api/readme-importer"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; export const initializeWithReadme = async ({ readmeUrl, @@ -14,7 +14,9 @@ export const initializeWithReadme = async ({ versionOfCli: string; }): Promise => { if (!readmeUrl || !isURL(readmeUrl)) { - taskContext.failAndThrow("Provide a URL to a readme-generated site"); + taskContext.failAndThrow("Provide a URL to a readme-generated site", undefined, { + code: CliError.Code.ConfigError + }); return; } diff --git a/packages/cli/library-docs-generator/cpp/src/renderers/CompoundPageRenderer.ts b/packages/cli/library-docs-generator/cpp/src/renderers/CompoundPageRenderer.ts index 21a53e92816..2dce1173f6c 100644 --- a/packages/cli/library-docs-generator/cpp/src/renderers/CompoundPageRenderer.ts +++ b/packages/cli/library-docs-generator/cpp/src/renderers/CompoundPageRenderer.ts @@ -4,6 +4,7 @@ * Handles: class, concept, function, enum, typedef, variable */ +import { CliError } from "@fern-api/task-context"; import type { CppClassIr, CppConceptIr, @@ -50,7 +51,10 @@ export function renderCompoundPage(compound: CppCompoundIr, meta: CompoundMeta): return renderVariablePage(compound.data, meta); default: { const _exhaustive: never = compound; - throw new Error(`Unknown compound kind: ${JSON.stringify(_exhaustive)}`); + throw new CliError({ + message: `Unknown compound kind: ${JSON.stringify(_exhaustive)}`, + code: CliError.Code.InternalError + }); } } } diff --git a/packages/cli/library-docs-generator/package.json b/packages/cli/library-docs-generator/package.json index 11ec38b3e34..5a0cdc51e52 100644 --- a/packages/cli/library-docs-generator/package.json +++ b/packages/cli/library-docs-generator/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@fern-api/fdr-sdk": "catalog:", + "@fern-api/task-context": "workspace:*", "js-yaml": "catalog:" }, "devDependencies": { diff --git a/packages/cli/library-docs-generator/src/CppDocsGenerator.ts b/packages/cli/library-docs-generator/src/CppDocsGenerator.ts index d4efc006dd2..76b74fc9583 100644 --- a/packages/cli/library-docs-generator/src/CppDocsGenerator.ts +++ b/packages/cli/library-docs-generator/src/CppDocsGenerator.ts @@ -11,6 +11,7 @@ * currentPagePath) requires that pages are rendered one at a time. */ +import { CliError } from "@fern-api/task-context"; import type { CompoundMeta } from "../cpp/src/context.js"; import { clearEntityRegistry, @@ -244,7 +245,10 @@ function categoryFolderForCompound(collected: CollectedCompound): string { return "variables"; default: { const _exhaustive: never = collected.compound; - throw new Error(`Unknown compound kind: ${JSON.stringify(_exhaustive)}`); + throw new CliError({ + message: `Unknown compound kind: ${JSON.stringify(_exhaustive)}`, + code: CliError.Code.InternalError + }); } } } diff --git a/packages/cli/login/src/askToLogin.ts b/packages/cli/login/src/askToLogin.ts index 10686c6b857..cd976d66bb1 100644 --- a/packages/cli/login/src/askToLogin.ts +++ b/packages/cli/login/src/askToLogin.ts @@ -1,5 +1,5 @@ import { FernToken, getToken, isLoggedIn } from "@fern-api/auth"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import inquirer, { ConfirmQuestion } from "inquirer"; import { login } from "./login.js"; @@ -8,7 +8,7 @@ export async function askToLogin(context: TaskContext): Promise { if (!(await isLoggedIn()) && process.stdout.isTTY) { await context.takeOverTerminal(async () => { if (!(await askForConfirmation("Login required. Continue?"))) { - context.failAndThrow(); + context.failAndThrow(undefined, undefined, { code: CliError.Code.AuthError }); } }); await login(context); @@ -16,7 +16,9 @@ export async function askToLogin(context: TaskContext): Promise { const token = await getToken(); if (token == null) { context.failAndThrow( - "Authentication required. Please run 'fern login' or set the FERN_TOKEN environment variable." + "Authentication required. Please run 'fern login' or set the FERN_TOKEN environment variable.", + undefined, + { code: CliError.Code.AuthError } ); } diff --git a/packages/cli/login/src/auth0-login/doAuth0DeviceAuthorizationFlow.ts b/packages/cli/login/src/auth0-login/doAuth0DeviceAuthorizationFlow.ts index 74d168559ed..3913064936c 100644 --- a/packages/cli/login/src/auth0-login/doAuth0DeviceAuthorizationFlow.ts +++ b/packages/cli/login/src/auth0-login/doAuth0DeviceAuthorizationFlow.ts @@ -1,5 +1,5 @@ import { delay } from "@fern-api/core-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import axios from "axios"; import boxen from "boxen"; import open from "open"; @@ -48,7 +48,7 @@ export async function doAuth0DeviceAuthorizationFlow({ }); if (deviceCodeResponse.status !== 200) { - context.failAndThrow("Failed to authenticate", deviceCodeResponse.data); + context.failAndThrow("Failed to authenticate", deviceCodeResponse.data, { code: CliError.Code.AuthError }); } await open(deviceCodeResponse.data.verification_uri_complete); @@ -134,6 +134,6 @@ async function pollForToken({ case "access_denied": case "expired_token": default: - return context.failAndThrow("Failed to authenticate", data); + return context.failAndThrow("Failed to authenticate", data, { code: CliError.Code.AuthError }); } } diff --git a/packages/cli/login/src/auth0-login/doAuth0LoginFlow.ts b/packages/cli/login/src/auth0-login/doAuth0LoginFlow.ts index c3b6890160c..4ba22070546 100644 --- a/packages/cli/login/src/auth0-login/doAuth0LoginFlow.ts +++ b/packages/cli/login/src/auth0-login/doAuth0LoginFlow.ts @@ -1,7 +1,7 @@ +import { CliError } from "@fern-api/task-context"; import axios from "axios"; import { IncomingMessage, Server } from "http"; import open from "open"; - import { createServer } from "./createServer.js"; /** @@ -127,10 +127,10 @@ async function getTokenFromCode({ ); const { access_token: accessToken, id_token: idToken } = response.data; if (accessToken == null) { - throw new Error("Access token is not defined"); + throw new CliError({ message: "Access token is not defined", code: CliError.Code.AuthError }); } if (idToken == null) { - throw new Error("ID token is not defined"); + throw new CliError({ message: "ID token is not defined", code: CliError.Code.AuthError }); } return { accessToken, idToken }; } diff --git a/packages/cli/login/src/auth0-login/resolveSsoConnection.ts b/packages/cli/login/src/auth0-login/resolveSsoConnection.ts index bcda5c11d48..760a569d994 100644 --- a/packages/cli/login/src/auth0-login/resolveSsoConnection.ts +++ b/packages/cli/login/src/auth0-login/resolveSsoConnection.ts @@ -1,3 +1,4 @@ +import { CliError } from "@fern-api/task-context"; import axios from "axios"; export async function resolveSsoConnection({ @@ -18,17 +19,26 @@ export async function resolveSsoConnection({ ); } catch (error) { if (axios.isAxiosError(error) && error.response?.status === 404) { - throw new Error(`No SSO connection associated with email: ${email}`); + throw new CliError({ + message: `No SSO connection associated with email: ${email}`, + code: CliError.Code.AuthError + }); } throw error; } const responseData = response.data; if (typeof responseData !== "object" || responseData == null) { - throw new Error("Invalid response format from SSO resolve endpoint"); + throw new CliError({ + message: "Invalid response format from SSO resolve endpoint", + code: CliError.Code.AuthError + }); } const { connection } = responseData; if (typeof connection !== "string" || connection.length === 0) { - throw new Error("No SSO connection found for this email"); + throw new CliError({ + message: "No SSO connection found for this email", + code: CliError.Code.AuthError + }); } return connection; } diff --git a/packages/cli/project-loader/src/loadProject.ts b/packages/cli/project-loader/src/loadProject.ts index ad25a059fbf..b5d01acdaa3 100644 --- a/packages/cli/project-loader/src/loadProject.ts +++ b/packages/cli/project-loader/src/loadProject.ts @@ -13,7 +13,7 @@ import { OPENAPI_DIRECTORY } from "@fern-api/configuration-loader"; import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import { handleFailedWorkspaceParserResult, loadAPIWorkspace, loadDocsWorkspace } from "@fern-api/workspace-loader"; import chalk from "chalk"; import { readdir } from "fs/promises"; @@ -44,7 +44,9 @@ export declare namespace loadProject { export async function loadProject({ context, nameOverride, ...args }: loadProject.Args): Promise { const fernDirectory = await getFernDirectory(nameOverride); if (fernDirectory == null) { - return context.failAndThrow(`Directory "${nameOverride ?? FERN_DIRECTORY}" not found.`); + return context.failAndThrow(`Directory "${nameOverride ?? FERN_DIRECTORY}" not found.`, undefined, { + code: CliError.Code.ConfigError + }); } return await loadProjectFromDirectory({ @@ -101,7 +103,9 @@ export async function loadProjectFromDirectory({ ` › ${ASYNCAPI_DIRECTORY}/\n` + `For more information:\n` + ` › SDK project structure: https://buildwithfern.com/learn/api-definitions/overview/project-structure\n` + - ` › Docs project structure: https://buildwithfern.com/learn/docs/getting-started/project-structure` + ` › Docs project structure: https://buildwithfern.com/learn/docs/getting-started/project-structure`, + undefined, + { code: CliError.Code.ConfigError } ); } @@ -147,10 +151,12 @@ export async function loadApis({ if (commandLineApiWorkspace != null) { if (!apiWorkspaceDirectoryNames.includes(commandLineApiWorkspace)) { - return context.failAndThrow("API does not exist: " + commandLineApiWorkspace); + return context.failAndThrow("API does not exist: " + commandLineApiWorkspace, undefined, { + code: CliError.Code.ConfigError + }); } } else if (apiWorkspaceDirectoryNames.length === 0) { - return context.failAndThrow("No APIs found."); + return context.failAndThrow("No APIs found.", undefined, { code: CliError.Code.ConfigError }); } else if (apiWorkspaceDirectoryNames.length > 1 && !defaultToAllApiWorkspaces) { let message = "There are multiple workspaces. You must specify one with --api:\n"; const longestWorkspaceName = Math.max( @@ -164,7 +170,7 @@ export async function loadApis({ )}`; }) .join("\n"); - return context.failAndThrow(message); + return context.failAndThrow(message, undefined, { code: CliError.Code.ConfigError }); } const apiWorkspaces: AbstractAPIWorkspace[] = []; @@ -188,7 +194,7 @@ export async function loadApis({ apiWorkspaces.push(workspace.workspace); } else { handleFailedWorkspaceParserResult(workspace, context.logger); - context.failAndThrow(); + context.failAndThrow(undefined, undefined, { code: CliError.Code.ConfigError }); } }) ); diff --git a/packages/cli/workspace/api-workspace-validator/src/validateAPIWorkspaceAndLogIssues.ts b/packages/cli/workspace/api-workspace-validator/src/validateAPIWorkspaceAndLogIssues.ts index e5fea63f8ed..6f253103438 100644 --- a/packages/cli/workspace/api-workspace-validator/src/validateAPIWorkspaceAndLogIssues.ts +++ b/packages/cli/workspace/api-workspace-validator/src/validateAPIWorkspaceAndLogIssues.ts @@ -3,7 +3,7 @@ import { ValidationViolation, validateFernWorkspace } from "@fern-api/fern-defin import { validateGeneratorsWorkspace } from "@fern-api/generators-validator"; import { OSSWorkspace } from "@fern-api/lazy-fern-workspace"; import { validateOSSWorkspace } from "@fern-api/oss-validator"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import validatePackageName from "validate-npm-package-name"; import { logViolations } from "./logViolations.js"; @@ -25,7 +25,7 @@ export async function collectAPIWorkspaceViolations({ ossWorkspace?: OSSWorkspace; }): Promise { if (!validatePackageName(workspace.definition.rootApiFile.contents.name).validForNewPackages) { - context.failAndThrow("API name is not valid."); + context.failAndThrow("API name is not valid.", undefined, { code: CliError.Code.ValidationError }); } const startTime = performance.now(); @@ -91,7 +91,7 @@ export async function validateAPIWorkspaceAndLogIssues({ ossWorkspace?: OSSWorkspace; }): Promise { if (!validatePackageName(workspace.definition.rootApiFile.contents.name).validForNewPackages) { - context.failAndThrow("API name is not valid."); + context.failAndThrow("API name is not valid.", undefined, { code: CliError.Code.ValidationError }); } const { hasErrors } = await validateAPIWorkspaceWithoutExiting({ @@ -102,6 +102,6 @@ export async function validateAPIWorkspaceAndLogIssues({ }); if (hasErrors) { - context.failAndThrow(); + context.failAndThrow(undefined, undefined, { code: CliError.Code.ValidationError }); } } diff --git a/packages/cli/workspace/browser-compatible-fern-workspace/src/InMemoryOpenAPILoader.ts b/packages/cli/workspace/browser-compatible-fern-workspace/src/InMemoryOpenAPILoader.ts index d40049c0127..209992847f4 100644 --- a/packages/cli/workspace/browser-compatible-fern-workspace/src/InMemoryOpenAPILoader.ts +++ b/packages/cli/workspace/browser-compatible-fern-workspace/src/InMemoryOpenAPILoader.ts @@ -1,8 +1,8 @@ import { isOpenAPIV2 } from "@fern-api/api-workspace-commons"; import { applyOpenAPIOverlay, mergeWithOverrides as coreMergeWithOverrides, type Overlay } from "@fern-api/core-utils"; import { getParseOptions, OpenAPIDocument } from "@fern-api/openapi-ir-parser"; +import { CliError } from "@fern-api/task-context"; import { OpenAPI, OpenAPIV3 } from "openapi-types"; - import { OpenAPIWorkspace } from "./OpenAPIWorkspace.js"; export class InMemoryOpenAPILoader { @@ -28,7 +28,10 @@ export class InMemoryOpenAPILoader { overlays: Overlay | undefined; }): OpenAPIV3.Document { if (isOpenAPIV2(openapi)) { - throw new Error("Swagger v2.0 is not supported in the browser"); + throw new CliError({ + message: "Swagger v2.0 is not supported in the browser", + code: CliError.Code.ConfigError + }); } let result = openapi as OpenAPIV3.Document; diff --git a/packages/cli/workspace/browser-compatible-fern-workspace/src/convertApiSpecToFdrDefinition.ts b/packages/cli/workspace/browser-compatible-fern-workspace/src/convertApiSpecToFdrDefinition.ts index e384d772b75..0bb9e100e1c 100644 --- a/packages/cli/workspace/browser-compatible-fern-workspace/src/convertApiSpecToFdrDefinition.ts +++ b/packages/cli/workspace/browser-compatible-fern-workspace/src/convertApiSpecToFdrDefinition.ts @@ -1,8 +1,8 @@ import { generatorsYml } from "@fern-api/configuration"; import { FdrAPI } from "@fern-api/fdr-sdk"; +import { CliError } from "@fern-api/task-context"; import { OpenrpcDocument } from "@open-rpc/meta-schema"; import { OpenAPIV3_1 } from "openapi-types"; - import { convertAsyncApiSpecToFdrDefinition } from "./convertAsyncApiSpecToFdrDefinition.js"; import { convertOpenApiSpecToFdrDefinition } from "./convertOpenApiSpecToFdrDefinition.js"; import { convertOpenRpcSpecToFdrDefinition } from "./convertOpenRpcSpecToFdrDefinition.js"; @@ -78,17 +78,20 @@ export async function apiSpecToFdr({ const detectedType = detectApiSpecType(spec); if (detectedType == null) { - throw new Error( - "Unable to detect API spec type. Expected a document with an 'openapi', 'swagger', 'asyncapi', or 'openrpc' top-level field." - ); + throw new CliError({ + message: + "Unable to detect API spec type. Expected a document with an 'openapi', 'swagger', 'asyncapi', or 'openrpc' top-level field.", + code: CliError.Code.ConfigError + }); } switch (detectedType) { case "openapi": { if (typeof spec.swagger === "string") { - throw new Error( - `Swagger v2.0 is not supported. Please convert your spec to OpenAPI 3.x first. Detected swagger version: ${spec.swagger}` - ); + throw new CliError({ + message: `Swagger v2.0 is not supported. Please convert your spec to OpenAPI 3.x first. Detected swagger version: ${spec.swagger}`, + code: CliError.Code.ConfigError + }); } return convertOpenApiSpecToFdrDefinition({ spec: spec as unknown as OpenAPIV3_1.Document, diff --git a/packages/cli/workspace/browser-compatible-fern-workspace/src/convertAsyncApiSpecToFdrDefinition.ts b/packages/cli/workspace/browser-compatible-fern-workspace/src/convertAsyncApiSpecToFdrDefinition.ts index 9f609ba886e..4b73ef99069 100644 --- a/packages/cli/workspace/browser-compatible-fern-workspace/src/convertAsyncApiSpecToFdrDefinition.ts +++ b/packages/cli/workspace/browser-compatible-fern-workspace/src/convertAsyncApiSpecToFdrDefinition.ts @@ -4,7 +4,8 @@ import { generatorsYml } from "@fern-api/configuration"; import { FdrAPI } from "@fern-api/fdr-sdk"; import { IntermediateRepresentation } from "@fern-api/ir-sdk"; import { convertIrToFdrApi } from "@fern-api/register"; -import { createMockTaskContext } from "@fern-api/task-context"; +import { CliError, createMockTaskContext } from "@fern-api/task-context"; + import { ErrorCollector } from "@fern-api/v3-importer-commons"; import { OpenAPIWorkspace } from "./OpenAPIWorkspace.js"; @@ -84,7 +85,10 @@ async function convertAsyncApiSpecToIr({ const result = await converter.convert(); if (result == null) { - throw new Error("Failed to convert AsyncAPI spec to intermediate representation"); + throw new CliError({ + message: "Failed to convert AsyncAPI spec to intermediate representation", + code: CliError.Code.IrConversionError + }); } if (errorCollector.hasErrors()) { diff --git a/packages/cli/workspace/browser-compatible-fern-workspace/src/convertOpenApiSpecToFdrDefinition.ts b/packages/cli/workspace/browser-compatible-fern-workspace/src/convertOpenApiSpecToFdrDefinition.ts index 308eb04dd20..e8c2213f32b 100644 --- a/packages/cli/workspace/browser-compatible-fern-workspace/src/convertOpenApiSpecToFdrDefinition.ts +++ b/packages/cli/workspace/browser-compatible-fern-workspace/src/convertOpenApiSpecToFdrDefinition.ts @@ -4,7 +4,8 @@ import { FdrAPI } from "@fern-api/fdr-sdk"; import { IntermediateRepresentation } from "@fern-api/ir-sdk"; import { OpenAPI3_1Converter, OpenAPIConverterContext3_1 } from "@fern-api/openapi-to-ir"; import { convertIrToFdrApi } from "@fern-api/register"; -import { createMockTaskContext } from "@fern-api/task-context"; +import { CliError, createMockTaskContext } from "@fern-api/task-context"; + import { ErrorCollector } from "@fern-api/v3-importer-commons"; import { OpenAPIV3_1 } from "openapi-types"; @@ -98,7 +99,10 @@ async function convertOpenApiSpecToIr({ const result = await converter.convert(); if (result == null) { - throw new Error("Failed to convert OpenAPI spec to intermediate representation"); + throw new CliError({ + message: "Failed to convert OpenAPI spec to intermediate representation", + code: CliError.Code.IrConversionError + }); } if (errorCollector.hasErrors()) { diff --git a/packages/cli/workspace/browser-compatible-fern-workspace/src/convertOpenRpcSpecToFdrDefinition.ts b/packages/cli/workspace/browser-compatible-fern-workspace/src/convertOpenRpcSpecToFdrDefinition.ts index bb425bad0ff..93ee19f92d2 100644 --- a/packages/cli/workspace/browser-compatible-fern-workspace/src/convertOpenRpcSpecToFdrDefinition.ts +++ b/packages/cli/workspace/browser-compatible-fern-workspace/src/convertOpenRpcSpecToFdrDefinition.ts @@ -4,7 +4,8 @@ import { FdrAPI } from "@fern-api/fdr-sdk"; import { IntermediateRepresentation } from "@fern-api/ir-sdk"; import { OpenRPCConverter, OpenRPCConverterContext3_1 } from "@fern-api/openrpc-to-ir"; import { convertIrToFdrApi } from "@fern-api/register"; -import { createMockTaskContext } from "@fern-api/task-context"; +import { CliError, createMockTaskContext } from "@fern-api/task-context"; + import { ErrorCollector } from "@fern-api/v3-importer-commons"; import { OpenrpcDocument } from "@open-rpc/meta-schema"; @@ -87,7 +88,10 @@ async function convertOpenRpcSpecToIr({ const result = await converter.convert(); if (result == null) { - throw new Error("Failed to convert OpenRPC spec to intermediate representation"); + throw new CliError({ + message: "Failed to convert OpenRPC spec to intermediate representation", + code: CliError.Code.IrConversionError + }); } if (errorCollector.hasErrors()) { diff --git a/packages/cli/workspace/loader/src/loadAsyncAPIFile.ts b/packages/cli/workspace/loader/src/loadAsyncAPIFile.ts index d69772270f9..c70161882fb 100644 --- a/packages/cli/workspace/loader/src/loadAsyncAPIFile.ts +++ b/packages/cli/workspace/loader/src/loadAsyncAPIFile.ts @@ -1,5 +1,5 @@ import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import { readdir } from "fs/promises"; export async function getValidAbsolutePathToAsyncAPIFromFolder( @@ -8,7 +8,9 @@ export async function getValidAbsolutePathToAsyncAPIFromFolder( ): Promise { const files = await readdir(absolutePathToAsyncAPIFolder); if (files.length < 1 || files[0] == null) { - context.failAndThrow(`No AsyncAPI found in directory ${absolutePathToAsyncAPIFolder}`); + context.failAndThrow(`No AsyncAPI found in directory ${absolutePathToAsyncAPIFolder}`, undefined, { + code: CliError.Code.ConfigError + }); } const absolutePathToAsyncAPIFile = join(absolutePathToAsyncAPIFolder, RelativeFilePath.of(files[0])); return absolutePathToAsyncAPIFile; @@ -21,7 +23,9 @@ export async function getValidAbsolutePathToAsyncAPI( const absolutePathToAsyncAPIExists = await doesPathExist(absolutePathToAsyncAPI); if (!absolutePathToAsyncAPIExists) { context.failAndThrow( - `AsyncAPI spec not found at ${absolutePathToAsyncAPIExists}. Please update the path in generators.yml` + `AsyncAPI spec not found at ${absolutePathToAsyncAPIExists}. Please update the path in generators.yml`, + undefined, + { code: CliError.Code.ConfigError } ); } return absolutePathToAsyncAPI; diff --git a/packages/cli/workspace/loader/src/loadDocsWorkspace.ts b/packages/cli/workspace/loader/src/loadDocsWorkspace.ts index 2b08ce6df77..123cdf55234 100644 --- a/packages/cli/workspace/loader/src/loadDocsWorkspace.ts +++ b/packages/cli/workspace/loader/src/loadDocsWorkspace.ts @@ -1,7 +1,8 @@ import { DOCS_CONFIGURATION_FILENAME, docsYml } from "@fern-api/configuration-loader"; import { extractErrorMessage, sanitizeNullValues, validateAgainstJsonSchema } from "@fern-api/core-utils"; import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; + import { readFile } from "fs/promises"; import yaml from "js-yaml"; @@ -90,9 +91,15 @@ export async function loadRawDocsConfiguration({ context.logger.error(`Parsing failed even after sanitization: ${extractErrorMessage(err)}`); // Log the JSON structure to debug context.logger.debug(`Sanitized JSON structure: ${JSON.stringify(sanitizedJson, null, 2)}`); - throw new Error(`Failed to parse ${absolutePathOfConfiguration}: ${extractErrorMessage(err)}`); + throw new CliError({ + message: `Failed to parse ${absolutePathOfConfiguration}: ${extractErrorMessage(err)}`, + code: CliError.Code.ParseError + }); } } else { - throw new Error(`Failed to parse docs.yml:\n${result.error?.message ?? "Unknown error"}`); + throw new CliError({ + message: `Failed to parse docs.yml:\n${result.error?.message ?? "Unknown error"}`, + code: CliError.Code.ParseError + }); } } diff --git a/packages/cli/workspace/loader/src/loadOpenAPIFile.ts b/packages/cli/workspace/loader/src/loadOpenAPIFile.ts index 68eedfac1e0..78d24536662 100644 --- a/packages/cli/workspace/loader/src/loadOpenAPIFile.ts +++ b/packages/cli/workspace/loader/src/loadOpenAPIFile.ts @@ -1,5 +1,5 @@ import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; import { readdir } from "fs/promises"; export async function getValidAbsolutePathToOpenAPIFromFolder( @@ -8,7 +8,9 @@ export async function getValidAbsolutePathToOpenAPIFromFolder( ): Promise { const files = await readdir(absolutePathToOpenAPIFolder); if (files.length < 1 || files[0] == null) { - context.failAndThrow(`No OpenAPI found in directory ${absolutePathToOpenAPIFolder}`); + context.failAndThrow(`No OpenAPI found in directory ${absolutePathToOpenAPIFolder}`, undefined, { + code: CliError.Code.ConfigError + }); } const absolutePathToOpenAPIFile = join(absolutePathToOpenAPIFolder, RelativeFilePath.of(files[0])); return absolutePathToOpenAPIFile; @@ -21,7 +23,9 @@ export async function getValidAbsolutePathToOpenAPI( const absolutePathToOpenAPIExists = await doesPathExist(absolutePathToOpenAPI); if (!absolutePathToOpenAPIExists) { context.failAndThrow( - `OpenAPI spec not found at ${absolutePathToOpenAPI}. Please update the path in generators.yml` + `OpenAPI spec not found at ${absolutePathToOpenAPI}. Please update the path in generators.yml`, + undefined, + { code: CliError.Code.ConfigError } ); } return absolutePathToOpenAPI; diff --git a/packages/cli/workspace/oss-validator/src/testing-utils/getViolationsForRule.ts b/packages/cli/workspace/oss-validator/src/testing-utils/getViolationsForRule.ts index a5cd5218869..dded78832d4 100644 --- a/packages/cli/workspace/oss-validator/src/testing-utils/getViolationsForRule.ts +++ b/packages/cli/workspace/oss-validator/src/testing-utils/getViolationsForRule.ts @@ -1,6 +1,7 @@ import { AbsoluteFilePath } from "@fern-api/fs-utils"; import { OSSWorkspace } from "@fern-api/lazy-fern-workspace"; -import { createMockTaskContext } from "@fern-api/task-context"; +import { CliError, createMockTaskContext } from "@fern-api/task-context"; + import { loadAPIWorkspace } from "@fern-api/workspace-loader"; import stripAnsi from "strip-ansi"; @@ -30,11 +31,14 @@ export async function getViolationsForRule({ }); if (!result.didSucceed) { - throw new Error("API workspace failed to load"); + throw new CliError({ message: "API workspace failed to load", code: CliError.Code.InternalError }); } if (!(result.workspace instanceof OSSWorkspace)) { - throw new Error("Expected an OSS workspace but got a different type"); + throw new CliError({ + message: "Expected an OSS workspace but got a different type", + code: CliError.Code.InternalError + }); } const violations = await runRulesOnOSSWorkspace({ diff --git a/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/collect-links.ts b/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/collect-links.ts index c844b383a98..0247a77534a 100644 --- a/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/collect-links.ts +++ b/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/collect-links.ts @@ -7,6 +7,7 @@ import { walkEstreeJsxAttributes } from "@fern-api/docs-markdown-utils"; import { AbsoluteFilePath, dirname, RelativeFilePath, resolve } from "@fern-api/fs-utils"; +import { CliError } from "@fern-api/task-context"; import type { Node as EstreeNode } from "estree"; import { walk } from "estree-walker"; import type { Root as HastRoot } from "hast"; @@ -62,7 +63,10 @@ export function collectLinksAndSources({ do { loopCount++; if (loopCount > LOOP_LIMIT) { - throw new Error("Infinite loop detected while collecting links and sources"); + throw new CliError({ + message: "Infinite loop detected while collecting links and sources", + code: CliError.Code.ReferenceError + }); } const popped = contentQueue.shift(); if (popped == null) { @@ -74,7 +78,10 @@ export function collectLinksAndSources({ // NOTE: we don't want to visit the same file multiple times if (absoluteFilepath != null) { if (visitedAbsoluteFilepaths.has(absoluteFilepath)) { - throw new Error(`Circular import detected: ${absoluteFilepath}`); + throw new CliError({ + message: `Circular import detected: ${absoluteFilepath}`, + code: CliError.Code.ReferenceError + }); } visitedAbsoluteFilepaths.add(absoluteFilepath); } diff --git a/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/valid-markdown-link.ts b/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/valid-markdown-link.ts index 8f6fa27951b..dda1e1ec326 100644 --- a/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/valid-markdown-link.ts +++ b/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/valid-markdown-link.ts @@ -6,7 +6,8 @@ import { APIV1Read, ApiDefinition, FernNavigation } from "@fern-api/fdr-sdk"; import { AbsoluteFilePath, join, RelativeFilePath, relative } from "@fern-api/fs-utils"; import { generateIntermediateRepresentation } from "@fern-api/ir-generator"; import { createLogger } from "@fern-api/logger"; -import { createMockTaskContext } from "@fern-api/task-context"; +import { CliError, createMockTaskContext } from "@fern-api/task-context"; + import chalk from "chalk"; import { randomUUID } from "crypto"; import path from "path"; @@ -47,7 +48,7 @@ export const ValidMarkdownLinks: Rule = { const configRoot = resolvedDocsDefinition.config.root; if (!configRoot || !isV1RootNode(configRoot)) { - throw new Error("Root node not found"); + throw new CliError({ message: "Root node not found", code: CliError.Code.InternalError }); } // TODO: this is a bit of a hack to get the navigation tree. We should probably just use the navigation tree diff --git a/packages/cli/yaml/docs-validator/src/testing-utils/getViolationsForRule.ts b/packages/cli/yaml/docs-validator/src/testing-utils/getViolationsForRule.ts index 6abb3a80db9..f4ccdf1a460 100644 --- a/packages/cli/yaml/docs-validator/src/testing-utils/getViolationsForRule.ts +++ b/packages/cli/yaml/docs-validator/src/testing-utils/getViolationsForRule.ts @@ -1,7 +1,8 @@ import { filterOssWorkspaces } from "@fern-api/docs-resolver"; import { AbsoluteFilePath } from "@fern-api/fs-utils"; import { loadProjectFromDirectory } from "@fern-api/project-loader"; -import { createMockTaskContext } from "@fern-api/task-context"; +import { CliError, createMockTaskContext } from "@fern-api/task-context"; + import stripAnsi from "strip-ansi"; import { Rule } from "../Rule.js"; @@ -30,7 +31,10 @@ export async function getViolationsForRule({ }); if (project.docsWorkspaces == null) { - throw new Error("Expected docs workspace to be present, but found none"); + throw new CliError({ + message: "Expected docs workspace to be present, but found none", + code: CliError.Code.InternalError + }); } const violations = await runRulesOnDocsWorkspace({ diff --git a/packages/cli/yaml/generators-validator/src/rules/compatible-ir-versions/compatible-ir-versions.ts b/packages/cli/yaml/generators-validator/src/rules/compatible-ir-versions/compatible-ir-versions.ts index 5a9c4663e1c..ac0a523c3af 100644 --- a/packages/cli/yaml/generators-validator/src/rules/compatible-ir-versions/compatible-ir-versions.ts +++ b/packages/cli/yaml/generators-validator/src/rules/compatible-ir-versions/compatible-ir-versions.ts @@ -1,6 +1,7 @@ import { addDefaultDockerOrgIfNotPresent } from "@fern-api/configuration-loader"; import { createFdrGeneratorsSdkService, getIrVersionForGenerator } from "@fern-api/core"; import { isVersionAhead } from "@fern-api/semver-utils"; +import { CliError } from "@fern-api/task-context"; import { Rule, RuleViolation } from "../../Rule.js"; @@ -9,7 +10,16 @@ function getMaybeBadVersionMessage( minCliVersion: string, cliVersion: string ): RuleViolation[] | undefined { - if (!isVersionAhead(cliVersion, minCliVersion)) { + let isAhead: boolean; + try { + isAhead = isVersionAhead(cliVersion, minCliVersion); + } catch (error) { + throw new CliError({ + message: `Failed to compare versions: ${error instanceof Error ? error.message : String(error)}`, + code: CliError.Code.VersionError + }); + } + if (!isAhead) { return [ { severity: "fatal", diff --git a/packages/cli/yaml/loader/package.json b/packages/cli/yaml/loader/package.json index 77fefb3b992..b7bb6165bf8 100644 --- a/packages/cli/yaml/loader/package.json +++ b/packages/cli/yaml/loader/package.json @@ -31,6 +31,9 @@ "test:debug": "pnpm run test --inspect --no-file-parallelism", "test:update": "vitest --passWithNoTests --run -u" }, + "dependencies": { + "@fern-api/task-context": "workspace:*" + }, "devDependencies": { "@fern-api/configs": "workspace:*", "@fern-api/core-utils": "workspace:*", diff --git a/packages/cli/yaml/loader/src/YamlConfigLoader.ts b/packages/cli/yaml/loader/src/YamlConfigLoader.ts index 46d44fd81d6..b0ecaed463f 100644 --- a/packages/cli/yaml/loader/src/YamlConfigLoader.ts +++ b/packages/cli/yaml/loader/src/YamlConfigLoader.ts @@ -1,6 +1,7 @@ import { extractErrorMessage } from "@fern-api/core-utils"; import { AbsoluteFilePath, RelativeFilePath, relative } from "@fern-api/fs-utils"; import { type Sourced, SourceLocation } from "@fern-api/source"; +import { CliError } from "@fern-api/task-context"; import { z } from "zod"; import { deepStrict } from "./deepStrict.js"; import { ReferenceResolver } from "./ReferenceResolver.js"; @@ -8,6 +9,7 @@ import { ValidationIssue } from "./ValidationIssue.js"; import type { YamlDocument } from "./YamlDocument.js"; import { YamlParser } from "./YamlParser.js"; import { YamlSourceResolver } from "./YamlSourceResolver.js"; + export namespace YamlConfigLoader { export type Result = Success | Failure; @@ -129,7 +131,10 @@ export class YamlConfigLoader { try { return await this.parser.parseDocument({ absoluteFilePath, cwd: this.cwd }); } catch (err) { - throw new Error(`Failed to parse YAML file ${absoluteFilePath}: ${extractErrorMessage(err)}`); + throw new CliError({ + message: `Failed to parse YAML file ${absoluteFilePath}: ${extractErrorMessage(err)}`, + code: CliError.Code.ParseError + }); } } diff --git a/packages/cli/yaml/loader/src/YamlSourceResolver.ts b/packages/cli/yaml/loader/src/YamlSourceResolver.ts index ed3ee5d0e54..8f3fe3c4fc9 100644 --- a/packages/cli/yaml/loader/src/YamlSourceResolver.ts +++ b/packages/cli/yaml/loader/src/YamlSourceResolver.ts @@ -7,6 +7,7 @@ import { SourcedNumber, SourcedString } from "@fern-api/source"; +import { CliError } from "@fern-api/task-context"; import type { YamlDocument, YamlPath } from "./YamlDocument.js"; export class YamlSourceResolver { @@ -46,7 +47,10 @@ export class YamlSourceResolver { case "boolean": return new SourcedBoolean(value, location) as Sourced; default: - throw new Error(`Unexpected value type: ${typeof value}`); + throw new CliError({ + message: `Unexpected value type: ${typeof value}`, + code: CliError.Code.InternalError + }); } } } diff --git a/packages/commons/api-workspace-commons/src/checkVersionExists.ts b/packages/commons/api-workspace-commons/src/checkVersionExists.ts index cc9fb8f2874..12acdc87e7d 100644 --- a/packages/commons/api-workspace-commons/src/checkVersionExists.ts +++ b/packages/commons/api-workspace-commons/src/checkVersionExists.ts @@ -1,6 +1,6 @@ import type { generatorsYml } from "@fern-api/configuration"; import { extractErrorMessage } from "@fern-api/core-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { CliError, TaskContext } from "@fern-api/task-context"; /** * Resolves the package name from the raw generator configuration. * @@ -152,7 +152,9 @@ export async function checkVersionDoesNotAlreadyExist({ context.failAndThrow( `Version ${version} of ${resolvedPackageName} already exists on the ${getRegistryName(language)} registry. ` + `Please use a different version number. ` + - `If you want to automatically increment the version, omit the --version flag.` + `If you want to automatically increment the version, omit the --version flag.`, + undefined, + { code: CliError.Code.VersionError } ); } else { context.logger.warn( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 038c14a002c..06b97df18ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3680,6 +3680,9 @@ importers: '@fern-api/ir-utils': specifier: workspace:* version: link:../../../commons/ir-utils + '@fern-api/task-context': + specifier: workspace:* + version: link:../../task-context '@fern-api/v3-importer-commons': specifier: workspace:* version: link:../v3-importer-commons @@ -3775,6 +3778,9 @@ importers: '@fern-api/importer-commons': specifier: workspace:* version: link:../../commons + '@fern-api/task-context': + specifier: workspace:* + version: link:../../../task-context js-yaml: specifier: 'catalog:' version: 4.1.1 @@ -4136,6 +4142,9 @@ importers: '@fern-api/logger': specifier: workspace:* version: link:../../logger + '@fern-api/task-context': + specifier: workspace:* + version: link:../../task-context js-yaml: specifier: 'catalog:' version: 4.1.1 @@ -6410,6 +6419,9 @@ importers: '@fern-api/fdr-sdk': specifier: 1.1.13-c1ad12a2b8 version: 1.1.13-c1ad12a2b8(@opentelemetry/api@1.9.1)(typescript@5.9.3) + '@fern-api/task-context': + specifier: workspace:* + version: link:../task-context js-yaml: specifier: 'catalog:' version: 4.1.1 @@ -7328,6 +7340,10 @@ importers: version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@22.19.17)(@vitest/coverage-v8@4.1.4)(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) packages/cli/yaml/loader: + dependencies: + '@fern-api/task-context': + specifier: workspace:* + version: link:../../task-context devDependencies: '@fern-api/configs': specifier: workspace:*