more strict error type checking in most packages and backend plugins

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-10-08 17:41:29 +02:00
parent 9d18bc8ba4
commit 36e67d2f24
68 changed files with 248 additions and 104 deletions
+22
View File
@@ -0,0 +1,22 @@
---
'@backstage/backend-common': patch
'@backstage/cli': patch
'@backstage/config-loader': patch
'@backstage/core-app-api': patch
'@backstage/core-components': patch
'@backstage/core-plugin-api': patch
'@backstage/create-app': patch
'@backstage/techdocs-common': patch
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-catalog': patch
'@backstage/plugin-catalog-backend': patch
'@backstage/plugin-catalog-backend-module-ldap': patch
'@backstage/plugin-catalog-import': patch
'@backstage/plugin-catalog-react': patch
'@backstage/plugin-code-coverage-backend': patch
'@backstage/plugin-kubernetes-backend': patch
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-techdocs-backend': patch
---
Internal updates to apply more strict checks to throw errors.
@@ -17,6 +17,7 @@
import knexFactory, { Knex } from 'knex';
import { Config } from '@backstage/config';
import { ForwardedError } from '@backstage/errors';
import { mergeDatabaseConfig } from '../config';
import { DatabaseConnector } from '../types';
import defaultNameOverride from './defaultNameOverride';
@@ -94,8 +95,7 @@ function requirePgConnectionString() {
try {
return require('pg-connection-string').parse;
} catch (e) {
const message = `Postgres: Install 'pg-connection-string'`;
throw new Error(`${message}\n${e.message}`);
throw new ForwardedError("Postgres: Install 'pg-connection-string'", e);
}
}
@@ -167,7 +167,7 @@ describe('AwsS3UrlReader', () => {
),
).rejects.toThrow(
Error(
`Could not retrieve file from S3: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
`Could not retrieve file from S3; caused by Error: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
),
);
});
@@ -216,7 +216,7 @@ describe('AwsS3UrlReader', () => {
),
).rejects.toThrow(
Error(
`Could not retrieve file from S3: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
`Could not retrieve file from S3; caused by Error: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
),
);
});
@@ -27,6 +27,7 @@ import {
} from './types';
import getRawBody from 'raw-body';
import { AwsS3Integration, ScmIntegrations } from '@backstage/integration';
import { ForwardedError } from '@backstage/errors';
import { ListObjectsV2Output, ObjectList } from 'aws-sdk/clients/s3';
const parseURL = (
@@ -162,7 +163,7 @@ export class AwsS3UrlReader implements UrlReader {
etag: etag,
};
} catch (e) {
throw new Error(`Could not retrieve file from S3: ${e.message}`);
throw new ForwardedError('Could not retrieve file from S3', e);
}
}
@@ -203,7 +204,7 @@ export class AwsS3UrlReader implements UrlReader {
return await this.deps.treeResponseFactory.fromReadableArray(responses);
} catch (e) {
throw new Error(`Could not retrieve file tree from S3: ${e.message}`);
throw new ForwardedError('Could not retrieve file tree from S3', e);
}
}
@@ -15,6 +15,7 @@
*/
import { ConfigReader } from '@backstage/config';
import { isError } from '@backstage/errors';
import { getVoidLogger } from '../logging';
import { UrlReaders } from './UrlReaders';
@@ -71,7 +72,10 @@ function withRetries(count: number, fn: () => Promise<void>) {
error = err;
}
}
if (!error.message.match(/rate limit|Too Many Requests/)) {
if (
isError(error) &&
!error.message.match(/rate limit|Too Many Requests/)
) {
throw error;
} else {
console.warn('Request was rate limited', error);
@@ -16,6 +16,7 @@
import Docker from 'dockerode';
import fs from 'fs-extra';
import { ForwardedError } from '@backstage/errors';
import { PassThrough } from 'stream';
import { ContainerRunner, RunContainerOptions } from './ContainerRunner';
@@ -45,8 +46,9 @@ export class DockerContainerRunner implements ContainerRunner {
try {
await this.dockerClient.ping();
} catch (e) {
throw new Error(
`This operation requires Docker. Docker does not appear to be available. Docker.ping() failed with: ${e.message}`,
throw new ForwardedError(
'This operation requires Docker. Docker does not appear to be available. Docker.ping() failed with',
e,
);
}
+1
View File
@@ -31,6 +31,7 @@
"@backstage/cli-common": "^0.1.4",
"@backstage/config": "^0.1.10",
"@backstage/config-loader": "^0.6.10",
"@backstage/errors": "^0.1.2",
"@hot-loader/react-dom": "^16.13.0",
"@lerna/package-graph": "^4.0.0",
"@lerna/project": "^4.0.0",
@@ -24,6 +24,7 @@ import camelCase from 'lodash/camelCase';
import upperFirst from 'lodash/upperFirst';
import os from 'os';
import { Command } from 'commander';
import { assertError } from '@backstage/errors';
import {
parseOwnerIds,
addCodeownersEntry,
@@ -173,6 +174,7 @@ async function buildPlugin(pluginFolder: string) {
);
});
} catch (error) {
assertError(error);
Task.error(error.message);
break;
}
@@ -329,6 +331,7 @@ export default async (cmd: Command) => {
Task.log();
Task.exit();
} catch (error) {
assertError(error);
Task.error(error.message);
Task.log('It seems that something went wrong when creating the plugin 🤔');
+2
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { assertError } from '@backstage/errors';
import { CommanderStatic } from 'commander';
import { exitWithError } from '../lib/errors';
@@ -252,6 +253,7 @@ function lazy(
process.exit(0);
} catch (error) {
assertError(error);
exitWithError(error);
}
};
@@ -20,6 +20,7 @@ import inquirer, { Answers, Question } from 'inquirer';
import { getCodeownersFilePath } from '../../lib/codeowners';
import { paths } from '../../lib/paths';
import { Task } from '../../lib/tasks';
import { assertError } from '@backstage/errors';
const BACKSTAGE = '@backstage';
@@ -35,6 +36,7 @@ export const checkExists = async (rootDir: string, pluginName: string) => {
);
}
} catch (e) {
assertError(e);
throw new Error(
chalk.red(
` There was an error removing plugin ${chalk.cyan(pluginName)}: ${
@@ -51,6 +53,7 @@ export const removePluginDirectory = async (destination: string) => {
try {
await fse.remove(destination);
} catch (e) {
assertError(e);
throw Error(
chalk.red(
` There was a problem removing the plugin directory: ${e.message}`,
@@ -67,6 +70,7 @@ export const removeSymLink = async (destination: string) => {
try {
await fse.remove(destination);
} catch (e) {
assertError(e);
throw Error(
chalk.red(
` Could not remove symbolic link\t${chalk.cyan(destination)}: ${
@@ -106,6 +110,7 @@ export const removeReferencesFromPluginsFile = async (
try {
await removeAllStatementsContainingID(pluginsFile, pluginNameCapitalized);
} catch (e) {
assertError(e);
throw new Error(
chalk.red(
` There was an error removing export statement for plugin ${chalk.cyan(
@@ -125,6 +130,7 @@ export const removePluginFromCodeOwners = async (
try {
await removeAllStatementsContainingID(codeOwnersFile, pluginName);
} catch (e) {
assertError(e);
throw new Error(
chalk.red(
` There was an error removing code owners statement for plugin ${chalk.cyan(
@@ -165,6 +171,7 @@ export const removeReferencesFromAppPackage = async (
'utf-8',
);
} catch (e) {
assertError(e);
throw new Error(
chalk.red(
` Failed to remove plugin as dependency in app: ${chalk.cyan(
@@ -245,6 +252,7 @@ export default async () => {
);
Task.log();
} catch (error) {
assertError(error);
Task.error(error.message);
Task.log('It seems that something went wrong when removing the plugin 🤔');
}
+3 -2
View File
@@ -17,6 +17,7 @@
import fs from 'fs-extra';
import chalk from 'chalk';
import semver from 'semver';
import { isError } from '@backstage/errors';
import { resolve as resolvePath } from 'path';
import { run } from '../../lib/run';
import { paths } from '../../lib/paths';
@@ -59,7 +60,7 @@ export default async () => {
try {
target = await findTargetVersion(name);
} catch (error) {
if (error.name === 'NotFoundError') {
if (isError(error) && error.name === 'NotFoundError') {
console.log(`Package info not found, ignoring package ${name}`);
return;
}
@@ -97,7 +98,7 @@ export default async () => {
try {
target = await findTargetVersion(name);
} catch (error) {
if (error.name === 'NotFoundError') {
if (isError(error) && error.name === 'NotFoundError') {
console.log(`Package info not found, ignoring package ${name}`);
return;
}
+8 -3
View File
@@ -23,6 +23,7 @@ import {
import { ExitCodeError } from './errors';
import { promisify } from 'util';
import { LogFunc } from './logging';
import { assertError, ForwardedError } from '@backstage/errors';
const execFile = promisify(execFileCb);
@@ -75,10 +76,14 @@ export async function runPlain(cmd: string, ...args: string[]) {
const { stdout } = await execFile(cmd, args, { shell: true });
return stdout.trim();
} catch (error) {
if (error.stderr) {
process.stderr.write(error.stderr);
assertError(error);
if ('stderr' in error) {
process.stderr.write(error.stderr as Buffer);
}
throw new ExitCodeError(error.code, [cmd, ...args].join(' '));
if (typeof error.code === 'number') {
throw new ExitCodeError(error.code, [cmd, ...args].join(' '));
}
throw new ForwardedError('Unknown execution error', error);
}
}
+1
View File
@@ -32,6 +32,7 @@
"dependencies": {
"@backstage/cli-common": "^0.1.4",
"@backstage/config": "^0.1.9",
"@backstage/errors": "^0.1.2",
"@types/json-schema": "^7.0.6",
"ajv": "^7.0.3",
"chokidar": "^3.5.2",
+2
View File
@@ -15,6 +15,7 @@
*/
import { AppConfig, JsonObject } from '@backstage/config';
import { assertError } from '@backstage/errors';
const ENV_PREFIX = 'APP_CONFIG_';
@@ -96,6 +97,7 @@ function safeJsonParse(str: string): [Error | null, any] {
try {
return [null, JSON.parse(str)];
} catch (err) {
assertError(err);
return [err, str];
}
}
@@ -24,6 +24,7 @@ import {
import { ConfigSchemaPackageEntry } from './types';
import { getProgramFromFiles, generateSchema } from 'typescript-json-schema';
import { JsonObject } from '@backstage/config';
import { assertError } from '@backstage/errors';
type Item = {
name?: string;
@@ -189,6 +190,7 @@ function compileTsSchemas(paths: string[]) {
[path.split(sep).join('/')], // Unix paths are expected for all OSes here
) as JsonObject | null;
} catch (error) {
assertError(error);
if (error.message !== 'type Config not found') {
throw error;
}
@@ -15,6 +15,7 @@
*/
import { JsonObject, JsonValue } from '@backstage/config';
import { assertError } from '@backstage/errors';
import { TransformFunc } from './types';
import { isObject } from './utils';
@@ -46,6 +47,7 @@ export async function applyConfigTransforms(
break;
}
} catch (error) {
assertError(error);
throw new Error(`error at ${path}, ${error.message}`);
}
}
+2 -3
View File
@@ -19,6 +19,7 @@ import yaml from 'yaml';
import chokidar from 'chokidar';
import { resolve as resolvePath, dirname, isAbsolute, basename } from 'path';
import { AppConfig } from '@backstage/config';
import { ForwardedError } from '@backstage/errors';
import {
applyConfigTransforms,
readEnvConfig,
@@ -114,9 +115,7 @@ export async function loadConfig(
try {
fileConfigs = await loadConfigFiles();
} catch (error) {
throw new Error(
`Failed to read static configuration file, ${error.message}`,
);
throw new ForwardedError('Failed to read static configuration file', error);
}
const envConfigs = await readEnvConfig(process.env);
@@ -45,25 +45,25 @@ describe('UrlPatternDiscovery', () => {
it('should validate that the pattern is a valid URL', () => {
expect(() => {
UrlPatternDiscovery.compile('example.com');
}).toThrow('Invalid discovery URL pattern, Invalid URL: example.com');
}).toThrow("Invalid discovery URL pattern, URL 'example.com' is invalid");
expect(() => {
UrlPatternDiscovery.compile('http://');
}).toThrow('Invalid discovery URL pattern, Invalid URL: http://');
}).toThrow("Invalid discovery URL pattern, URL 'http://' is invalid");
expect(() => {
UrlPatternDiscovery.compile('abc123');
}).toThrow('Invalid discovery URL pattern, Invalid URL: abc123');
}).toThrow("Invalid discovery URL pattern, URL 'abc123' is invalid");
expect(() => {
UrlPatternDiscovery.compile('http://example.com:{{pluginId}}');
}).toThrow(
'Invalid discovery URL pattern, Invalid URL: http://example.com:pluginId',
"Invalid discovery URL pattern, URL 'http://example.com:pluginId' is invalid",
);
expect(() => {
UrlPatternDiscovery.compile('/{{pluginId}}');
}).toThrow('Invalid discovery URL pattern, Invalid URL: /pluginId');
}).toThrow("Invalid discovery URL pattern, URL '/pluginId' is invalid");
expect(() => {
UrlPatternDiscovery.compile('http://localhost/{{pluginId}}?forbidden');
@@ -16,6 +16,8 @@
import { DiscoveryApi } from '@backstage/core-plugin-api';
const ERROR_PREFIX = 'Invalid discovery URL pattern,';
/**
* UrlPatternDiscovery is a lightweight DiscoveryApi implementation.
* It uses a single template string to construct URLs for each plugin.
@@ -30,21 +32,22 @@ export class UrlPatternDiscovery implements DiscoveryApi {
*/
static compile(pattern: string): UrlPatternDiscovery {
const parts = pattern.split(/\{\{\s*pluginId\s*\}\}/);
const urlStr = parts.join('pluginId');
let url;
try {
const urlStr = parts.join('pluginId');
const url = new URL(urlStr);
if (url.hash) {
throw new Error('URL must not have a hash');
}
if (url.search) {
throw new Error('URL must not have a query');
}
if (urlStr.endsWith('/')) {
throw new Error('URL must not end with a slash');
}
} catch (error) {
throw new Error(`Invalid discovery URL pattern, ${error.message}`);
url = new URL(urlStr);
} catch {
throw new Error(`${ERROR_PREFIX} URL '${urlStr}' is invalid`);
}
if (url.hash) {
throw new Error(`${ERROR_PREFIX} URL must not have a hash`);
}
if (url.search) {
throw new Error(`${ERROR_PREFIX} URL must not have a query`);
}
if (urlStr.endsWith('/')) {
throw new Error(`${ERROR_PREFIX} URL must not end with a slash`);
}
return new UrlPatternDiscovery(parts);
@@ -83,7 +83,7 @@ const MockRouteSource = <T extends { [name in string]: string }>(props: {
} catch (ex) {
return (
<div>
Error at {props.name}: {ex.message}
Error at {props.name}, {String(ex)}
</div>
);
}
@@ -293,12 +293,12 @@ describe('discovery', () => {
expect(
rendered.getByText(
`Error at outsideWithParams: Cannot route to ${ref3} with parent ${ref5} as it has parameters`,
`Error at outsideWithParams, Error: Cannot route to ${ref3} with parent ${ref5} as it has parameters`,
),
).toBeInTheDocument();
expect(
rendered.getByText(
`Error at outsideNoParams: Cannot route to ${ref3} with parent ${ref5} as it has parameters`,
`Error at outsideNoParams, Error: Cannot route to ${ref3} with parent ${ref5} as it has parameters`,
),
).toBeInTheDocument();
});
@@ -21,6 +21,7 @@ import ListItemText from '@material-ui/core/ListItemText';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import React, { useState } from 'react';
import { isError } from '@backstage/errors';
import { PendingAuthRequest } from '@backstage/core-plugin-api';
export type LoginRequestListItemClassKey = 'root';
@@ -42,14 +43,14 @@ type RowProps = {
const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => {
const classes = useItemStyles();
const [error, setError] = useState<Error>();
const [error, setError] = useState<string>();
const handleContinue = async () => {
setBusy(true);
try {
await request.trigger();
} catch (e) {
setError(e);
setError(isError(e) ? e.message : 'An unspecified error occurred');
} finally {
setBusy(false);
}
@@ -64,13 +65,7 @@ const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => {
</ListItemAvatar>
<ListItemText
primary={request.provider.title}
secondary={
error && (
<Typography color="error">
{error.message || 'An unspecified error occurred'}
</Typography>
)
}
secondary={error && <Typography color="error">{error}</Typography>}
/>
<Button color="primary" variant="contained" onClick={handleContinue}>
Log in
@@ -25,6 +25,7 @@ import {
auth0AuthApiRef,
errorApiRef,
} from '@backstage/core-plugin-api';
import { ForwardedError } from '@backstage/errors';
const Component: ProviderComponent = ({ onResult }) => {
const auth0AuthApi = useApi(auth0AuthApiRef);
@@ -53,7 +54,7 @@ const Component: ProviderComponent = ({ onResult }) => {
},
});
} catch (error) {
errorApi.post(error);
errorApi.post(new ForwardedError('Auth0 login failed', error));
}
};
@@ -26,6 +26,7 @@ import {
} from './types';
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
import { GridItem } from './styles';
import { ForwardedError } from '@backstage/errors';
const Component: ProviderComponent = ({ config, onResult }) => {
const { apiRef, title, message } = config as SignInProviderConfig;
@@ -55,7 +56,7 @@ const Component: ProviderComponent = ({ config, onResult }) => {
},
});
} catch (error) {
errorApi.post(error);
errorApi.post(new ForwardedError('Login failed', error));
}
};
@@ -51,12 +51,18 @@ export function createRoutableExtension<
try {
useRouteRef(mountPoint);
} catch (error) {
if (error?.message.startsWith('No path for ')) {
throw new Error(
`Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` +
'Routable extension components may not be rendered by other components and must be ' +
'directly available as an element within the App provider component.',
);
if (typeof error === 'object' && error !== null) {
const { message } = error as { message?: unknown };
if (
typeof message === 'string' &&
message.startsWith('No path for ')
) {
throw new Error(
`Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` +
'Routable extension components may not be rendered by other components and must be ' +
'directly available as an element within the App provider component.',
);
}
}
throw error;
}
+2 -4
View File
@@ -45,9 +45,7 @@ async function createTemporaryAppFolder(tempDir: string) {
try {
await fs.mkdir(tempDir);
} catch (error) {
throw new Error(
`Failed to create temporary app directory: ${error.message}`,
);
throw new Error(`Failed to create temporary app directory, ${error}`);
}
});
}
@@ -156,7 +154,7 @@ export default async (cmd: Command): Promise<void> => {
Task.log();
Task.exit();
} catch (error) {
Task.error(error.message);
Task.error(String(error));
Task.log('It seems that something went wrong when creating the app 🤔');
Task.log('We are going to clean up, and then you can try again.');
+1
View File
@@ -25,6 +25,7 @@
},
"devDependencies": {
"@backstage/cli-common": "^0.1.1",
"@backstage/errors": "^0.1.2",
"@types/fs-extra": "^9.0.1",
"@types/node": "^14.14.32",
"chalk": "^4.0.0",
+5 -2
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { assertError } from '@backstage/errors';
import {
spawn,
execFile as execFileCb,
@@ -66,11 +67,12 @@ export async function runPlain(cmd: string[], options?: SpawnOptions) {
});
return stdout.trim();
} catch (error) {
assertError(error);
if (error.stdout) {
process.stdout.write(error.stdout);
process.stdout.write(error.stdout as Buffer);
}
if (error.stderr) {
process.stderr.write(error.stderr);
process.stderr.write(error.stderr as Buffer);
}
throw error;
}
@@ -147,6 +149,7 @@ export async function waitForPageWithText(
);
break;
} catch (error) {
assertError(error);
if (error.message.match(EXPECTED_LOAD_ERRORS)) {
loadAttempts++;
if (loadAttempts >= maxLoadAttempts) {
@@ -16,6 +16,7 @@
import { isChildPath } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { assertError, ForwardedError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { spawn } from 'child_process';
import fs from 'fs-extra';
@@ -155,8 +156,9 @@ export const getMkdocsYml = async (
mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
} catch (error) {
throw new Error(
`Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml for validation: ${error.message}`,
throw new ForwardedError(
'Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml for validation',
error,
);
}
}
@@ -225,6 +227,7 @@ export const patchMkdocsYmlPreBuild = async (
try {
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
} catch (error) {
assertError(error);
logger.warn(
`Could not read MkDocs YAML config file ${mkdocsYmlPath} before running the generator: ${error.message}`,
);
@@ -241,6 +244,7 @@ export const patchMkdocsYmlPreBuild = async (
throw new Error('Bad YAML format.');
}
} catch (error) {
assertError(error);
logger.warn(
`Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${error.message}`,
);
@@ -279,6 +283,7 @@ export const patchMkdocsYmlPreBuild = async (
);
}
} catch (error) {
assertError(error);
logger.warn(
`Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`,
);
@@ -307,6 +312,7 @@ export const addBuildTimestampMetadata = async (
try {
json = await fs.readJson(techdocsMetadataPath);
} catch (err) {
assertError(err);
const message = `Invalid JSON at ${techdocsMetadataPath} with error ${err.message}`;
logger.error(message);
throw new Error(message);
@@ -36,6 +36,7 @@ import {
GeneratorRunInType,
GeneratorRunOptions,
} from './types';
import { ForwardedError } from '@backstage/errors';
export class TechdocsGenerator implements GeneratorBase {
/**
@@ -151,8 +152,9 @@ export class TechdocsGenerator implements GeneratorBase {
this.logger.debug(
`Failed to generate docs from ${inputDir} into ${outputDir}`,
);
throw new Error(
`Failed to generate docs from ${inputDir} into ${outputDir} with error ${error.message}`,
throw new ForwardedError(
'Failed to generate docs from ${inputDir} into ${outputDir}',
error,
);
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { NotModifiedError } from '@backstage/errors';
import { assertError } from '@backstage/errors';
import { UrlReader } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { Logger } from 'winston';
@@ -40,8 +40,9 @@ export class UrlPreparer implements PreparerBase {
logger: this.logger,
});
} catch (error) {
assertError(error);
// NotModifiedError means that etag based cache is still valid.
if (error instanceof NotModifiedError) {
if (error.name === 'NotModifiedError') {
this.logger.debug(`Cache is valid for etag ${options?.etag}`);
} else {
this.logger.debug(
@@ -286,7 +286,7 @@ describe('AwsS3Publish', () => {
const fails = publisher.fetchTechDocsMetadata(invalidEntityName);
await expect(fails).rejects.toMatchObject({
message: `TechDocs metadata fetch failed, The file ${techDocsMetadaFilePath} does not exist!`,
message: `TechDocs metadata fetch failed; caused by Error: The file ${techDocsMetadaFilePath} does not exist!`,
});
});
});
@@ -15,6 +15,7 @@
*/
import { Entity, EntityName } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { assertError, ForwardedError } from '@backstage/errors';
import aws, { Credentials } from 'aws-sdk';
import { ListObjectsV2Output } from 'aws-sdk/clients/s3';
import { CredentialsOptions } from 'aws-sdk/lib/credentials';
@@ -49,7 +50,7 @@ const streamToBuffer = (stream: Readable): Promise<Buffer> => {
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(chunks)));
} catch (e) {
throw new Error(`Unable to parse the response data ${e.message}`);
throw new ForwardedError('Unable to parse the response data', e);
}
});
};
@@ -204,6 +205,7 @@ export class AwsS3Publish implements PublisherBase {
prefix: remoteFolder,
});
} catch (e) {
assertError(e);
this.logger.error(
`Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,
);
@@ -312,12 +314,13 @@ export class AwsS3Publish implements PublisherBase {
resolve(techdocsMetadata);
} catch (err) {
assertError(err);
this.logger.error(err.message);
reject(new Error(err.message));
}
});
} catch (e) {
throw new Error(`TechDocs metadata fetch failed, ${e.message}`);
throw new ForwardedError('TechDocs metadata fetch failed', e);
}
}
@@ -351,6 +354,7 @@ export class AwsS3Publish implements PublisherBase {
res.send(await streamToBuffer(stream));
} catch (err) {
assertError(err);
this.logger.warn(
`TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`,
);
@@ -396,6 +400,7 @@ export class AwsS3Publish implements PublisherBase {
try {
newPath = lowerCaseEntityTripletInStoragePath(file);
} catch (e) {
assertError(e);
this.logger.warn(e.message);
return;
}
@@ -424,6 +429,7 @@ export class AwsS3Publish implements PublisherBase {
.promise();
}
} catch (e) {
assertError(e);
this.logger.warn(`Unable to migrate ${file}: ${e.message}`);
}
}, f),
@@ -201,7 +201,7 @@ describe('AzureBlobStoragePublish', () => {
let error;
try {
await publisher.publish({ entity, directory });
} catch (e) {
} catch (e: any) {
error = e;
}
@@ -305,7 +305,7 @@ describe('AzureBlobStoragePublish', () => {
const fails = publisher.fetchTechDocsMetadata(invalidEntityName);
await expect(fails).rejects.toMatchObject({
message: `TechDocs metadata fetch failed, The file ${techDocsMetadaFilePath} does not exist!`,
message: `TechDocs metadata fetch failed; caused by Error: The file ${techDocsMetadaFilePath} does not exist!`,
});
});
});
@@ -21,6 +21,7 @@ import {
} from '@azure/storage-blob';
import { Entity, EntityName } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { assertError, ForwardedError } from '@backstage/errors';
import express from 'express';
import JSON5 from 'json5';
import limiterFactory from 'p-limit';
@@ -132,6 +133,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
);
}
} catch (e) {
assertError(e);
this.logger.error(`from Azure Blob Storage client library: ${e.message}`);
}
@@ -165,6 +167,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
maxPageSize: BATCH_CONCURRENCY,
});
} catch (e) {
assertError(e);
this.logger.error(
`Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,
);
@@ -307,7 +310,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
);
return techdocsMetadata;
} catch (e) {
throw new Error(`TechDocs metadata fetch failed, ${e.message}`);
throw new ForwardedError('TechDocs metadata fetch failed', e);
}
}
@@ -386,6 +389,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
try {
newPath = lowerCaseEntityTripletInStoragePath(originalPath);
} catch (e) {
assertError(e);
this.logger.warn(e.message);
return;
}
@@ -395,6 +399,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
this.logger.verbose(`Migrating ${originalPath}`);
await this.renameBlob(originalPath, newPath, removeOriginal);
} catch (e) {
assertError(e);
this.logger.warn(`Unable to migrate ${originalPath}: ${e.message}`);
}
}
@@ -15,6 +15,7 @@
*/
import { Entity, EntityName } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { assertError } from '@backstage/errors';
import { File, FileExistsResponse, Storage } from '@google-cloud/storage';
import express from 'express';
import JSON5 from 'json5';
@@ -112,6 +113,7 @@ export class GoogleGCSPublish implements PublisherBase {
isAvailable: true,
};
} catch (err) {
assertError(err);
this.logger.error(
`Could not retrieve metadata about the GCS bucket ${this.bucketName}. ` +
'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' +
@@ -142,6 +144,7 @@ export class GoogleGCSPublish implements PublisherBase {
);
existingFiles = await this.getFilesForFolder(remoteFolder);
} catch (e) {
assertError(e);
this.logger.error(
`Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,
);
@@ -37,6 +37,7 @@ import {
getHeadersForFileExtension,
lowerCaseEntityTripletInStoragePath,
} from './helpers';
import { assertError } from '@backstage/errors';
// TODO: Use a more persistent storage than node_modules or /tmp directory.
// Make it configurable with techdocs.publisher.local.publishDirectory
@@ -134,6 +135,7 @@ export class LocalPublish implements PublisherBase {
try {
return await fs.readJson(metadataPath);
} catch (err) {
assertError(err);
this.logger.error(
`Unable to read techdocs_metadata.json at ${metadataPath}. Error: ${err}`,
);
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { assertError } from '@backstage/errors';
import { File } from '@google-cloud/storage';
import { Writable } from 'stream';
import { Logger } from 'winston';
@@ -42,6 +43,7 @@ export class MigrateWriteStream extends Writable {
try {
newFile = lowerCaseEntityTripletInStoragePath(file.name);
} catch (e) {
assertError(e);
this.logger.warn(e.message);
next();
return;
@@ -31,6 +31,7 @@ import {
ReadinessResponse,
TechDocsMetadata,
} from './types';
import { assertError, ForwardedError } from '@backstage/errors';
const streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {
return new Promise((resolve, reject) => {
@@ -40,7 +41,7 @@ const streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(chunks)));
} catch (e) {
throw new Error(`Unable to parse the response data ${e.message}`);
throw new ForwardedError('Unable to parse the response data', e);
}
});
};
@@ -118,6 +119,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
isAvailable: false,
};
} catch (err) {
assertError(err);
this.logger.error(`from OpenStack client library: ${err.message}`);
return {
isAvailable: false,
@@ -203,6 +205,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
resolve(techdocsMetadata);
} catch (err) {
assertError(err);
this.logger.error(err.message);
reject(new Error(err.message));
}
@@ -245,6 +248,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
res.send(await streamToBuffer(stream));
} catch (err) {
assertError(err);
this.logger.warn(
`TechDocs OpenStack swift router failed to serve content from container ${this.containerName} at path ${filePath}: ${err.message}`,
);
@@ -276,6 +280,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
}
return false;
} catch (err) {
assertError(err);
this.logger.warn(err.message);
return false;
}
@@ -22,7 +22,7 @@ import {
BackstageIdentity,
AuthProviderConfig,
} from '../../providers/types';
import { InputError, NotAllowedError } from '@backstage/errors';
import { InputError, isError, NotAllowedError } from '@backstage/errors';
import { TokenIssuer } from '../../identity/types';
import { readState, verifyNonce } from './helpers';
import { postMessageResponse, ensuresXRequestedWith } from '../flow';
@@ -153,13 +153,13 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
response,
});
} catch (error) {
const { name, message } = isError(error)
? error
: new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value
// post error message back to popup if failure
return postMessageResponse(res, appOrigin, {
type: 'authorization_response',
error: {
name: error.name,
message: error.message,
},
error: { name, message },
});
}
}
@@ -220,7 +220,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
res.status(200).json(response);
} catch (error) {
res.status(401).send(`${error.message}`);
res.status(401).send(String(error));
}
}
@@ -29,6 +29,7 @@ import {
import { AuthProviderRouteHandlers, AuthProviderFactory } from '../types';
import { postMessageResponse } from '../../lib/flow';
import { TokenIssuer } from '../../identity/types';
import { isError } from '@backstage/errors';
type SamlInfo = {
fullProfile: any;
@@ -93,12 +94,12 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers {
},
});
} catch (error) {
const { name, message } = isError(error)
? error
: new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value
return postMessageResponse(res, this.appUrl, {
type: 'authorization_response',
error: {
name: error.name,
message: error.message,
},
error: { name, message },
});
}
}
+2 -1
View File
@@ -26,7 +26,7 @@ import {
PluginDatabaseManager,
PluginEndpointDiscovery,
} from '@backstage/backend-common';
import { NotFoundError } from '@backstage/errors';
import { assertError, NotFoundError } from '@backstage/errors';
import { CatalogClient } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { createOidcRouter, DatabaseKeyStore, TokenFactory } from '../identity';
@@ -121,6 +121,7 @@ export async function createRouter({
router.use(`/${providerId}`, r);
} catch (e) {
assertError(e);
if (process.env.NODE_ENV !== 'development') {
throw new Error(
`Failed to initialize ${providerId} auth provider, ${e.message}`,
@@ -31,6 +31,7 @@
"dependencies": {
"@backstage/catalog-model": "^0.9.4",
"@backstage/config": "^0.1.10",
"@backstage/errors": "^0.1.2",
"@backstage/plugin-catalog-backend": "^0.17.0",
"@types/ldapjs": "^2.2.0",
"ldapjs": "^2.2.0",
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { ForwardedError } from '@backstage/errors';
import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs';
import { Logger } from 'winston';
import { BindConfig } from './config';
@@ -120,7 +121,7 @@ export class LdapClient {
clearInterval(logInterval);
});
} catch (e) {
throw new Error(`LDAP search at DN "${dn}" failed, ${e.message}`);
throw new ForwardedError(`LDAP search at DN "${dn}" failed`, e);
}
}
@@ -167,7 +168,7 @@ export class LdapClient {
});
});
} catch (e) {
throw new Error(`LDAP search at DN "${dn}" failed, ${e.message}`);
throw new ForwardedError(`LDAP search at DN "${dn}" failed`, e);
}
}
@@ -15,7 +15,7 @@
*/
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { ConflictError, NotFoundError } from '@backstage/errors';
import { ConflictError, isError, NotFoundError } from '@backstage/errors';
import { Knex } from 'knex';
import lodash from 'lodash';
import { v4 as uuid } from 'uuid';
@@ -550,7 +550,10 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
return result.rowCount === 1 || result.length === 1;
} catch (error) {
// SQLite reached this rather than the rowCount check above
if (error.message.includes('UNIQUE constraint failed')) {
if (
isError(error) &&
error.message.includes('UNIQUE constraint failed')
) {
return false;
}
throw error;
@@ -16,6 +16,7 @@
import { UrlReader } from '@backstage/backend-common';
import { LocationSpec } from '@backstage/catalog-model';
import { isError } from '@backstage/errors';
import limiterFactory from 'p-limit';
import * as result from './results';
import {
@@ -50,7 +51,7 @@ export class AwsS3DiscoveryProcessor implements CatalogProcessor {
} catch (error) {
const message = `Unable to read ${location.type}, ${error}`;
if (error.name === 'NotFoundError') {
if (isError(error) && error.name === 'NotFoundError') {
if (!optional) {
emit(result.notFoundError(location, message));
}
@@ -255,7 +255,7 @@ export class LocationReaders implements LocationReader {
const message = `Policy check failed while analyzing entity ${kind}:${namespace}/${name} at ${stringifyLocationReference(
item.location,
)}, ${e}`;
emit(result.inputError(item.location, e.message));
emit(result.inputError(item.location, message));
logger.warn(message);
return undefined;
}
@@ -19,7 +19,7 @@ import {
entityEnvelopeSchemaValidator,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { serializeError } from '@backstage/errors';
import { assertError, serializeError } from '@backstage/errors';
import { Hash } from 'crypto';
import stableStringify from 'fast-json-stable-stringify';
import { Logger } from 'winston';
@@ -257,6 +257,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
track.markSuccessfulWithChanges(setOfThingsToStitch.size);
} catch (error) {
assertError(error);
track.markFailed(error);
}
},
@@ -23,7 +23,12 @@ import {
stringifyEntityRef,
stringifyLocationReference,
} from '@backstage/catalog-model';
import { ConflictError, InputError, NotAllowedError } from '@backstage/errors';
import {
assertError,
ConflictError,
InputError,
NotAllowedError,
} from '@backstage/errors';
import { JsonValue } from '@backstage/config';
import { ScmIntegrationRegistry } from '@backstage/integration';
import path from 'path';
@@ -160,6 +165,7 @@ export class DefaultCatalogProcessingOrchestrator
ok: collectorResults.errors.length === 0,
};
} catch (error) {
assertError(error);
return {
ok: false,
errors: collector.results().errors.concat(error),
@@ -21,6 +21,7 @@ import {
ORIGIN_LOCATION_ANNOTATION,
stringifyLocationReference,
} from '@backstage/catalog-model';
import { assertError } from '@backstage/errors';
import { Logger } from 'winston';
import { CatalogProcessorResult } from '../ingestion';
import { locationSpecToLocationEntity } from '../util/conversion';
@@ -73,6 +74,7 @@ export class ProcessorOutputCollector {
try {
entity = validateEntityEnvelope(i.entity);
} catch (e) {
assertError(e);
this.logger.debug(`Envelope validation failed at ${i.location}, ${e}`);
this.errors.push(e);
return;
+1
View File
@@ -35,6 +35,7 @@
"@backstage/catalog-model": "^0.9.4",
"@backstage/core-components": "^0.7.0",
"@backstage/core-plugin-api": "^0.1.10",
"@backstage/errors": "^0.1.2",
"@backstage/integration": "^0.6.8",
"@backstage/integration-react": "^0.1.12",
"@backstage/plugin-catalog-react": "^0.6.0",
@@ -113,8 +113,8 @@ export const StepInitAnalyzeUrl = ({
break;
}
}
} catch (e) {
setError(e.data?.error?.message ?? e.message);
} catch (e: any) {
setError(e?.data?.error?.message ?? e.message);
setSubmitted(false);
}
},
@@ -16,6 +16,7 @@
import { Entity } from '@backstage/catalog-model';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import { assertError } from '@backstage/errors';
import {
catalogApiRef,
formatEntityRefTitle,
@@ -174,6 +175,7 @@ export const StepPrepareCreatePullRequest = ({
{ notRepeatable: true },
);
} catch (e) {
assertError(e);
setError(e.message);
setSubmitted(false);
}
@@ -25,6 +25,7 @@ import { PrepareResult, ReviewResult } from '../useImportState';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { Link } from '@backstage/core-components';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { assertError } from '@backstage/errors';
type Props = {
prepareResult: PrepareResult;
@@ -88,6 +89,7 @@ export const StepReviewLocation = ({
locations,
});
} catch (e) {
assertError(e);
// TODO: this error should be handled differently. We add it as 'optional' and
// it is not uncommon that a PR has not been merged yet.
if (
+1
View File
@@ -34,6 +34,7 @@
"@backstage/core-app-api": "^0.1.17",
"@backstage/core-components": "^0.7.0",
"@backstage/core-plugin-api": "^0.1.10",
"@backstage/errors": "^0.1.2",
"@backstage/integration": "^0.6.8",
"@backstage/version-bridge": "^0.1.0",
"@material-ui/core": "^4.12.2",
@@ -33,6 +33,7 @@ import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState
import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api';
import { Progress, ResponseErrorPanel } from '@backstage/core-components';
import { assertError } from '@backstage/errors';
const useStyles = makeStyles({
advancedButton: {
@@ -70,6 +71,7 @@ const Contents = ({
await state.unregisterLocation();
onConfirm();
} catch (err) {
assertError(err);
alertApi.post({ message: err.message });
} finally {
setBusy(false);
@@ -87,6 +89,7 @@ const Contents = ({
await state.deleteEntity();
onConfirm();
} catch (err) {
assertError(err);
alertApi.post({ message: err.message });
} finally {
setBusy(false);
@@ -19,6 +19,7 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { Button, Dialog, DialogActions, DialogTitle } from '@material-ui/core';
import React, { useState } from 'react';
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
import { assertError } from '@backstage/errors';
type Props = {
open: boolean;
@@ -44,6 +45,7 @@ export const DeleteEntityDialog = ({
await catalogApi.removeEntityByUid(uid!);
onConfirm();
} catch (err) {
assertError(err);
alertApi.post({ message: err.message });
} finally {
setBusy(false);
@@ -208,7 +208,7 @@ describe('CodeCoverageUtils', () => {
};
// @ts-ignore
utils.validateRequestBody(mockRequest as Request);
} catch (error) {
} catch (error: any) {
err = error;
}
expect(err?.message).toEqual('Content-Type missing');
@@ -226,7 +226,7 @@ describe('CodeCoverageUtils', () => {
// @ts-ignore
utils.validateRequestBody(mockRequest as Request);
} catch (error) {
} catch (error: any) {
err = error;
}
expect(err?.message).toEqual('Illegal Content-Type');
+1
View File
@@ -35,6 +35,7 @@
"@backstage/backend-common": "^0.9.4",
"@backstage/catalog-model": "^0.9.3",
"@backstage/config": "^0.1.10",
"@backstage/errors": "^0.1.2",
"@backstage/plugin-kubernetes-common": "^0.1.4",
"@google-cloud/container": "^2.2.0",
"@kubernetes/client-node": "^0.15.0",
@@ -213,7 +213,7 @@ describe('GkeClusterLocator', () => {
} as any);
await expect(sut.getClusters()).rejects.toThrow(
'There was an error retrieving clusters from GKE for projectId=some-project region=some-region : [some error]',
'There was an error retrieving clusters from GKE for projectId=some-project region=some-region; caused by Error: some error',
);
expect(mockedListClusters).toBeCalledTimes(1);
@@ -15,6 +15,7 @@
*/
import { Config } from '@backstage/config';
import { ForwardedError } from '@backstage/errors';
import * as container from '@google-cloud/container';
import { GKEClusterDetails, KubernetesClustersSupplier } from '../types/types';
@@ -65,8 +66,9 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
skipTLSVerify,
}));
} catch (e) {
throw new Error(
`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region} : [${e.message}]`,
throw new ForwardedError(
`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,
e,
);
}
}
@@ -17,6 +17,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration';
import { createTemplateAction } from '../../createTemplateAction';
import { OctokitProvider } from './OctokitProvider';
import { emitterEventNames } from '@octokit/webhooks';
import { assertError } from '@backstage/errors';
type ContentType = 'form' | 'json';
@@ -130,6 +131,7 @@ export function createGithubWebhookAction(options: {
});
ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
} catch (e) {
assertError(e);
ctx.logger.warn(
`Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`,
);
@@ -19,6 +19,7 @@ import { PassThrough, Writable } from 'stream';
import { Logger } from 'winston';
import { Git } from '@backstage/backend-common';
import { Octokit } from '@octokit/rest';
import { assertError } from '@backstage/errors';
export type RunCommandOptions = {
command: string;
@@ -152,6 +153,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({
},
});
} catch (e) {
assertError(e);
if (
e.message.includes(
'Upgrade to GitHub Pro or make this repository public to enable this feature',
@@ -22,6 +22,7 @@ import { getRepoSourceDirectory } from './util';
import { createTemplateAction } from '../../createTemplateAction';
import { Config } from '@backstage/config';
import { OctokitProvider } from '../github/OctokitProvider';
import { assertError } from '@backstage/errors';
type Permission = 'pull' | 'push' | 'admin' | 'maintain' | 'triage';
type Collaborator = { access: Permission; username: string };
@@ -198,6 +199,7 @@ export function createPublishGithubAction(options: {
permission,
});
} catch (e) {
assertError(e);
ctx.logger.warn(
`Skipping ${permission} access for ${team_slug}, ${e.message}`,
);
@@ -213,6 +215,7 @@ export function createPublishGithubAction(options: {
names: topics.map(t => t.toLowerCase()),
});
} catch (e) {
assertError(e);
ctx.logger.warn(`Skipping topics ${topics.join(' ')}, ${e.message}`);
}
}
@@ -250,6 +253,7 @@ export function createPublishGithubAction(options: {
requireCodeOwnerReviews,
});
} catch (e) {
assertError(e);
ctx.logger.warn(
`Skipping: default branch protection on '${newRepo.name}', ${e.message}`,
);
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { JsonObject } from '@backstage/config';
import { assertError } from '@backstage/errors';
import { Logger } from 'winston';
import {
CompletedTaskState,
@@ -185,6 +186,7 @@ export class StorageTaskBroker implements TaskBroker {
try {
callback(undefined, result);
} catch (error) {
assertError(error);
callback(error, { events: [] });
}
}
@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Task, TaskBroker, WorkflowRunner } from './types';
import { LegacyWorkflowRunner } from './LegacyWorkflowRunner';
import { assertError } from '@backstage/errors';
type Options = {
taskBroker: TaskBroker;
@@ -44,6 +46,7 @@ export class TaskWorker {
await task.complete('completed', { output });
} catch (error) {
assertError(error);
await task.complete('failed', {
error: { name: error.name, message: error.message },
});
@@ -21,6 +21,7 @@ import {
SOURCE_LOCATION_ANNOTATION,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { assertError } from '@backstage/errors';
import fs from 'fs-extra';
import os from 'os';
import { Logger } from 'winston';
@@ -39,6 +40,7 @@ export async function getWorkingDirectory(
await fs.access(workingDirectory, fs.constants.F_OK | fs.constants.W_OK);
logger.info(`using working directory: ${workingDirectory}`);
} catch (err) {
assertError(err);
logger.error(
`working directory ${workingDirectory} ${
err.code === 'ENOENT' ? 'does not exist' : 'is not writable'
@@ -19,7 +19,7 @@ import {
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { NotModifiedError } from '@backstage/errors';
import { assertError, isError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import {
GeneratorBase,
@@ -131,7 +131,7 @@ export class DocsBuilder {
preparedDir = preparerResponse.preparedDir;
newEtag = preparerResponse.etag;
} catch (err) {
if (err instanceof NotModifiedError) {
if (isError(err) && err.name === 'NotModifiedError') {
// No need to prepare anymore since cache is valid.
// Set last check happened to now
new BuildMetadataStorage(this.entity.metadata.uid).setLastUpdated();
@@ -142,7 +142,7 @@ export class DocsBuilder {
);
return false;
}
throw new Error(err.message);
throw err;
}
this.logger.info(
@@ -195,6 +195,7 @@ 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}`);
}
}
@@ -221,6 +222,7 @@ export class DocsBuilder {
`Removing generated directory ${outputDir} since the site has been published`,
);
} catch (error) {
assertError(error);
this.logger.debug(`Error removing generated directory ${error.message}`);
}
@@ -16,7 +16,7 @@
import { Entity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { NotFoundError } from '@backstage/errors';
import { assertError, NotFoundError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import {
GeneratorBuilder,
@@ -113,6 +113,7 @@ export class DocsSynchronizer {
return;
}
} catch (e) {
assertError(e);
const msg = `Failed to build the docs page: ${e.message}`;
taskLogger.error(msg);
this.logger.error(msg, e);