errors: add toError utility and migrate assertError usages

Add a `toError` utility function to `@backstage/errors` that converts
unknown values to `ErrorLike` objects. If the value is already error-like
it is returned as-is. Strings are used directly as the error message, and
other values are stringified with a fallback to JSON.stringify to avoid
unhelpful `[object Object]` messages.

Non-error causes passed to `CustomErrorBase` are now converted and stored
using `toError` rather than discarded. Existing `assertError` call sites
across the codebase are migrated to `toError`.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-04-03 01:33:34 +02:00
parent 3cdf048f77
commit b2319ffe45
70 changed files with 400 additions and 345 deletions
@@ -19,7 +19,7 @@ import {
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { assertError, isError } from '@backstage/errors';
import { isError, toError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import {
GeneratorBase,
@@ -225,9 +225,8 @@ export class DocsBuilder {
// Not a blocker hence no need to await this.
fs.remove(preparedDir);
} catch (error) {
assertError(error);
this.logger.debug(
`Error removing prepared directory ${error.message}`,
`Error removing prepared directory ${toError(error).message}`,
);
}
}
@@ -238,9 +237,8 @@ export class DocsBuilder {
// Not a blocker hence no need to await this.
fs.remove(outputDir);
} catch (error) {
assertError(error);
this.logger.debug(
`Error removing generated directory ${error.message}`,
`Error removing generated directory ${toError(error).message}`,
);
}
}
+4 -4
View File
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { assertError, CustomErrorBase } from '@backstage/errors';
import { CustomErrorBase, toError } from '@backstage/errors';
import { Config, readDurationFromConfig } from '@backstage/config';
import { CacheService, LoggerService } from '@backstage/backend-plugin-api';
import { durationToMilliseconds } from '@backstage/types';
@@ -77,9 +77,9 @@ export class TechDocsCache {
this.logger.debug(`Cache miss: ${path}`);
return response;
} catch (e) {
assertError(e);
this.logger.warn(`Error getting cache entry ${path}: ${e.message}`);
this.logger.debug(e.message, e);
const error = toError(e);
this.logger.warn(`Error getting cache entry ${path}: ${error.message}`);
this.logger.debug(error.message, error);
return undefined;
}
}
@@ -20,7 +20,7 @@ import {
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { assertError, NotFoundError } from '@backstage/errors';
import { NotFoundError, toError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import {
GeneratorBuilder,
@@ -146,13 +146,13 @@ export class DocsSynchronizer {
return;
}
} catch (e) {
assertError(e);
const buildError = toError(e);
const msg = `Failed to build the docs page for entity ${stringifyEntityRef(
entity,
)}: ${e.message}`;
)}: ${buildError.message}`;
taskLogger.error(msg);
this.logger.error(msg, e);
error(e);
this.logger.error(msg, buildError);
error(buildError);
return;
}
@@ -241,10 +241,9 @@ export class DocsSynchronizer {
finish({ updated: false });
}
} catch (e) {
assertError(e);
// In case of error, log and allow the user to go about their business.
this.logger.error(
`Error syncing cache for ${entityTripletPath}: ${e.message}`,
`Error syncing cache for ${entityTripletPath}: ${toError(e).message}`,
);
finish({ updated: false });
} finally {