Skip to content

Commit

Permalink
fix: make error messages compatible with semantic-release
Browse files Browse the repository at this point in the history
  • Loading branch information
iorate committed Jan 31, 2023
1 parent 1e0addf commit d581765
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 23 deletions.
5 changes: 0 additions & 5 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import SemanticReleaseError from '@semantic-release/error';
import template from 'lodash.template';
import type { Context } from 'semantic-release';
import { z } from 'zod';

export function createError(message: string, details?: string): SemanticReleaseError {
return new SemanticReleaseError(message, '\u{1f98a}', details);
}

export const pluginConfigSchema = z.object({
addonId: z.string(),
addonDirPath: z.string(),
Expand Down
8 changes: 6 additions & 2 deletions src/prepare.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import SemanticReleaseError from '@semantic-release/error';
import zipDir from 'zip-dir';
import { z } from 'zod';
import { FullContext, PluginConfig, applyContext, applyDefaults, createError } from './common';
import { FullContext, PluginConfig, applyContext, applyDefaults } from './common';

export async function prepare(
pluginConfig: Readonly<PluginConfig>,
Expand All @@ -25,7 +26,10 @@ export async function prepare(
try {
manifest = z.record(z.string(), z.unknown()).parse(JSON.parse(manifestJson));
} catch {
throw createError(`Invalid manifest.json: ${manifestJsonPath}`);
throw new SemanticReleaseError(
`An invalid manifest was read from ${manifestJsonPath}.`,
'EINVALIDMANIFEST',
);
}
manifest.version = nextRelease.version;
await fs.writeFile(manifestJsonPath, JSON.stringify(manifest, null, 2), 'utf8');
Expand Down
5 changes: 3 additions & 2 deletions src/publish.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SemanticReleaseError from '@semantic-release/error';
import { marked } from 'marked';
import { FullContext, PluginConfig, applyContext, applyDefaults, createError } from './common';
import { FullContext, PluginConfig, applyContext, applyDefaults } from './common';
import { UpdateAddonError, updateAddon } from './update-addon';

function parseReleaseNotes(releaseNotes: string): string {
Expand Down Expand Up @@ -51,7 +52,7 @@ export async function publish(
});
} catch (error: unknown) {
if (error instanceof UpdateAddonError) {
throw createError(error.message, error.details);
throw new SemanticReleaseError(error.message, error.code, error.details);
} else {
throw error;
}
Expand Down
31 changes: 22 additions & 9 deletions src/update-addon.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import fs from 'node:fs';
import util from 'node:util';
import axios, { AxiosResponse } from 'axios';
import FormData from 'form-data';
import jwt from 'jsonwebtoken';
import { z } from 'zod';

export class UpdateAddonError extends Error {
constructor(message: string, readonly details?: string) {
constructor(message: string, readonly code?: string, readonly details?: string) {
super(message);
this.name = 'UpdateAddonError';
}
Expand Down Expand Up @@ -70,6 +71,10 @@ export async function updateAddon({
}
}

function stringify(value: unknown): string {
return util.inspect(value, { breakLength: Infinity, depth: Infinity });
}

function jwtSign(key: string, secret: string): string {
const issuedAt = Math.floor(Date.now() / 1000);
const payload = {
Expand All @@ -81,10 +86,11 @@ function jwtSign(key: string, secret: string): string {
return jwt.sign(payload, secret, { algorithm: 'HS256' });
}

function throwBadResponse(path: string, response: AxiosResponse): never {
function throwBadResponse(url: string, response: AxiosResponse): never {
throw new UpdateAddonError(
`Bad response from ${path} with status ${response.status}`,
JSON.stringify(response.data),
`A bad response was received from ${url} with status ${response.status}.`,
'EBADRESPONSE',
stringify(response.data),
);
}

Expand All @@ -97,9 +103,10 @@ async function apiFetch<T, Schema extends z.ZodType<T>>(
body: Readonly<Record<string, unknown>> | FormData | null,
schema: Schema,
): Promise<T> {
const url = new URL(`/api/v5/addons/${path}`, baseURL).toString();
try {
const response = await axios({
url: new URL(`/api/v5/addons/${path}`, baseURL).toString(),
url,
method,
headers: {
Authorization: `JWT ${jwtSign(apiKey, apiSecret)}`,
Expand All @@ -108,12 +115,12 @@ async function apiFetch<T, Schema extends z.ZodType<T>>(
});
const result = schema.safeParse(response.data);
if (!result.success) {
throwBadResponse(path, response);
throwBadResponse(url, response);
}
return result.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
throwBadResponse(path, error.response);
throwBadResponse(url, error.response);
} else {
throw error;
}
Expand Down Expand Up @@ -178,7 +185,7 @@ function waitForValidation(apiParams: Readonly<APIParams>, uuid: string): Promis
if (intervalId) {
clearTimeout(intervalId);
}
reject(new UpdateAddonError('Validation timeout'));
reject(new UpdateAddonError('Validation timed out.', 'EVALIDATIONTIMEOUT'));
}, 300000); // 5 minutes
const poll = () =>
void getUpload(apiParams, uuid)
Expand All @@ -188,7 +195,13 @@ function waitForValidation(apiParams: Readonly<APIParams>, uuid: string): Promis
if (valid) {
resolve();
} else {
reject(new UpdateAddonError('Validation error', JSON.stringify(validation)));
reject(
new UpdateAddonError(
'Validation failed.',
'EVALIDATIONFAILURE',
stringify(validation),
),
);
}
} else {
intervalId = setTimeout(poll, 1000); // 1 second
Expand Down
29 changes: 24 additions & 5 deletions src/verify-conditions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import SemanticReleaseError from '@semantic-release/error';
import type { Context } from 'semantic-release';
import { fromZodError } from 'zod-validation-error';
import { createError, envSchema, pluginConfigSchema } from './common';
import { envSchema, pluginConfigSchema } from './common';

async function exists(path: string): Promise<boolean> {
try {
Expand All @@ -28,24 +29,42 @@ export async function verifyConditions(
const pluginConfigResult = pluginConfigSchema.safeParse(pluginConfig);
if (!pluginConfigResult.success) {
errors.push(
createError('Invalid plugin config', fromZodError(pluginConfigResult.error).message),
new SemanticReleaseError(
'The plugin configuration is invalid.',
'EINVALIDPLUGINCONFIG',
fromZodError(pluginConfigResult.error).message,
),
);
} else {
const { addonDirPath } = pluginConfigResult.data;
if (!(await exists(addonDirPath))) {
errors.push(createError(`Missing add-on directory: ${addonDirPath}`));
errors.push(
new SemanticReleaseError(
`The add-on directory is not found at ${addonDirPath}.`,
'EADDONDIRNOTFOUND',
),
);
} else {
const manifestJsonPath = path.join(addonDirPath, 'manifest.json');
if (!(await exists(manifestJsonPath))) {
errors.push(createError(`Missing manifest.json: ${manifestJsonPath}`));
errors.push(
new SemanticReleaseError(
`manifest.json is not found at ${manifestJsonPath}.`,
'EMANIFESTJSONNOTFOUND',
),
);
}
}
}

const envResult = envSchema.safeParse(env);
if (!envResult.success) {
errors.push(
createError('Invalid environment variables', fromZodError(envResult.error).message),
new SemanticReleaseError(
'The environment variables are invalid.',
'EINVALIDENV',
fromZodError(envResult.error).message,
),
);
}

Expand Down

0 comments on commit d581765

Please sign in to comment.