Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/actions/code-pushup/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type SourceFileIssue,
runInCI,
} from '@code-pushup/ci';
import { CODE_PUSHUP_UNICODE_LOGO } from '@code-pushup/utils';
import { CODE_PUSHUP_UNICODE_LOGO, stringifyError } from '@code-pushup/utils';

type GitHubRefs = {
head: GitBranch;
Expand Down Expand Up @@ -150,7 +150,7 @@ async function run(): Promise<void> {

core.info(`${LOG_PREFIX} Finished running successfully`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const message = stringifyError(error);
core.error(`${LOG_PREFIX} Failed: ${message}`);
core.setFailed(message);
}
Expand Down
28 changes: 15 additions & 13 deletions packages/ci/src/lib/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ export const interpolatedSlugSchema = slugSchema.catch(ctx => {
throw new ZodError(ctx.error.issues);
});

export const configPatternsSchema = z.object({
persist: persistConfigSchema.transform(persist => ({
...DEFAULT_PERSIST_CONFIG,
...persist,
})),
upload: uploadConfigSchema
.omit({ organization: true, project: true })
.extend({
organization: interpolatedSlugSchema,
project: interpolatedSlugSchema,
})
.optional(),
});
export const configPatternsSchema = z
.object({
persist: persistConfigSchema.transform(persist => ({
...DEFAULT_PERSIST_CONFIG,
...persist,
})),
upload: uploadConfigSchema
.omit({ organization: true, project: true })
.extend({
organization: interpolatedSlugSchema,
project: interpolatedSlugSchema,
})
.optional(),
})
.meta({ title: 'ConfigPatterns' });
10 changes: 4 additions & 6 deletions packages/ci/src/lib/settings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ZodError, z } from 'zod';
import { SchemaValidationError, validate } from '@code-pushup/models';
import type { ConfigPatterns, Settings } from './models.js';
import { configPatternsSchema } from './schemas.js';

Expand Down Expand Up @@ -32,17 +32,15 @@ export function parseConfigPatternsFromString(

try {
const json = JSON.parse(value);
return configPatternsSchema.parse(json);
return validate(configPatternsSchema, json);
} catch (error) {
if (error instanceof SyntaxError) {
throw new TypeError(
`Invalid JSON value for configPatterns input - ${error.message}`,
);
}
if (error instanceof ZodError) {
throw new TypeError(
`Invalid shape of configPatterns input:\n${z.prettifyError(error)}`,
);
if (error instanceof SchemaValidationError) {
throw new TypeError(`Invalid shape of configPatterns input:\n${error}`);
}
throw error;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/lib/implementation/core-config.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ansis from 'ansis';
import { describe, expect, vi } from 'vitest';
import {
type CoreConfig,
Expand Down Expand Up @@ -203,6 +204,11 @@ describe('parsing values from CLI and middleware', () => {
middlewares: [{ middlewareFunction: coreConfigMiddleware }],
},
).parseAsync(),
).rejects.toThrow('invalid_type');
).rejects.toThrow(`Invalid ${ansis.bold('UploadConfig')}
✖ Invalid input: expected string, received undefined
→ at server
✖ Invalid input: expected string, received undefined
→ at apiKey
`);
});
});
6 changes: 2 additions & 4 deletions packages/cli/src/lib/implementation/core-config.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
DEFAULT_PERSIST_OUTPUT_DIR,
type Format,
uploadConfigSchema,
validate,
} from '@code-pushup/models';
import type { CoreConfigCliOptions } from './core-config.model.js';
import type { FilterOptions } from './filter.model.js';
Expand Down Expand Up @@ -58,10 +59,7 @@ export async function coreConfigMiddleware<
const upload =
rcUpload == null && cliUpload == null
? undefined
: uploadConfigSchema.parse({
...rcUpload,
...cliUpload,
});
: validate(uploadConfigSchema, { ...rcUpload, ...cliUpload });

return {
...(config != null && { config }),
Expand Down
17 changes: 11 additions & 6 deletions packages/cli/src/lib/yargs-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import yargs, {
type Options,
type ParserConfigurationOptions,
} from 'yargs';
import { type PersistConfig, formatSchema } from '@code-pushup/models';
import {
type PersistConfig,
formatSchema,
validate,
} from '@code-pushup/models';
import { TERMINAL_WIDTH } from '@code-pushup/utils';
import {
descriptionStyle,
Expand Down Expand Up @@ -37,9 +41,8 @@ export const yargsDecorator = {
* returns configurable yargs CLI for code-pushup
*
* @example
* yargsCli(hideBin(process.argv))
* // bootstrap CLI; format arguments
* .argv;
* // bootstrap CLI; format arguments
* yargsCli(hideBin(process.argv)).argv;
*/
export function yargsCli<T = unknown>(
argv: string[],
Expand Down Expand Up @@ -150,11 +153,13 @@ function validatePersistFormat(persist: PersistConfig) {
if (persist.format != null) {
persist.format
.flatMap(format => format.split(','))
.forEach(format => formatSchema.parse(format));
.forEach(format => {
validate(formatSchema, format);
});
}
return true;
} catch {
throw new Error(
throw new TypeError(
`Invalid persist.format option. Valid options are: ${formatSchema.options.join(
', ',
)}`,
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/lib/collect-and-persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type CoreConfig,
type PersistConfig,
pluginReportSchema,
validate,
} from '@code-pushup/models';
import {
isVerbose,
Expand Down Expand Up @@ -57,6 +58,6 @@ export async function collectAndPersistReports(
// validate report and throw if invalid
reportResult.plugins.forEach(plugin => {
// Running checks after persisting helps while debugging as you can check the invalid output after the error is thrown
pluginReportSchema.parse(plugin);
validate(pluginReportSchema, plugin);
});
}
5 changes: 3 additions & 2 deletions packages/core/src/lib/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type ReportsDiff,
type UploadConfig,
reportSchema,
validate,
} from '@code-pushup/models';
import {
type Diff,
Expand Down Expand Up @@ -49,8 +50,8 @@ export async function compareReportFiles(
readJsonFile(options?.after ?? defaultInputPath('after')),
]);
const reports: Diff<Report> = {
before: reportSchema.parse(reportBefore),
after: reportSchema.parse(reportAfter),
before: validate(reportSchema, reportBefore),
after: validate(reportSchema, reportAfter),
};

const diff = compareReports(reports);
Expand Down
33 changes: 16 additions & 17 deletions packages/core/src/lib/implementation/execute-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
logMultipleResults,
pluralizeToken,
scoreAuditsWithTarget,
stringifyError,
} from '@code-pushup/utils';
import {
executePluginRunner,
Expand All @@ -34,16 +35,16 @@ import {
*
* @example
* // plugin execution
* const pluginCfg = pluginConfigSchema.parse({...});
* const pluginCfg = validate(pluginConfigSchema, {...});
* const output = await executePlugin(pluginCfg);
*
* @example
* // error handling
* try {
* await executePlugin(pluginCfg);
* } catch (e) {
* console.error(e.message);
* }
* @example
* // error handling
* try {
* await executePlugin(pluginCfg);
* } catch (error) {
* console.error(error);
* }
*/
export async function executePlugin(
pluginConfig: PluginConfig,
Expand Down Expand Up @@ -125,11 +126,9 @@ const wrapProgress = async (
} catch (error) {
progressBar?.incrementInSteps(steps);
throw new Error(
error instanceof Error
? `- Plugin ${bold(pluginCfg.title)} (${bold(
pluginCfg.slug,
)}) produced the following error:\n - ${error.message}`
: String(error),
`- Plugin ${bold(pluginCfg.title)} (${bold(
pluginCfg.slug,
)}) produced the following error:\n - ${stringifyError(error)}`,
);
}
};
Expand All @@ -144,14 +143,14 @@ const wrapProgress = async (
*
* @example
* // plugin execution
* const plugins = [pluginConfigSchema.parse({...})];
* const plugins = [validate(pluginConfigSchema, {...})];
*
* @example
* // error handling
* try {
* await executePlugins(plugins);
* } catch (e) {
* console.error(e.message); // Plugin output is invalid
* await executePlugins(plugins);
* } catch (error) {
* console.error(error); // Plugin output is invalid
* }
*
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/lib/implementation/persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
directoryExists,
generateMdReport,
logMultipleFileResults,
stringifyError,
ui,
} from '@code-pushup/utils';

Expand Down Expand Up @@ -51,7 +52,7 @@ export async function persistReport(
try {
await mkdir(outputDir, { recursive: true });
} catch (error) {
ui().logger.warning((error as Error).toString());
ui().logger.warning(stringifyError(error));
throw new PersistDirError(outputDir);
}
}
Expand Down
18 changes: 8 additions & 10 deletions packages/core/src/lib/implementation/read-rc-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {
type CoreConfig,
SUPPORTED_CONFIG_FILE_FORMATS,
coreConfigSchema,
validate,
} from '@code-pushup/models';
import { fileExists, importModule, parseSchema } from '@code-pushup/utils';
import { fileExists, importModule } from '@code-pushup/utils';

export class ConfigPathError extends Error {
constructor(configPath: string) {
Expand All @@ -14,27 +15,24 @@ export class ConfigPathError extends Error {
}

export async function readRcByPath(
filepath: string,
filePath: string,
tsconfig?: string,
): Promise<CoreConfig> {
if (filepath.length === 0) {
if (filePath.length === 0) {
throw new Error('The path to the configuration file is empty.');
}

if (!(await fileExists(filepath))) {
throw new ConfigPathError(filepath);
if (!(await fileExists(filePath))) {
throw new ConfigPathError(filePath);
}

const cfg: CoreConfig = await importModule({
filepath,
filepath: filePath,
tsconfig,
format: 'esm',
});

return parseSchema(coreConfigSchema, cfg, {
schemaType: 'core config',
sourcePath: filepath,
});
return validate(coreConfigSchema, cfg, { filePath });
}

export async function autoloadRc(tsconfig?: string): Promise<CoreConfig> {
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/lib/load-portal-client.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { ui } from '@code-pushup/utils';
import { stringifyError, ui } from '@code-pushup/utils';

export async function loadPortalClient(): Promise<
typeof import('@code-pushup/portal-client') | null
> {
try {
return await import('@code-pushup/portal-client');
} catch {
} catch (error) {
ui().logger.warning(
`Failed to import @code-pushup/portal-client - ${stringifyError(error)}`,
);
ui().logger.error(
'Optional peer dependency @code-pushup/portal-client is not available. Make sure it is installed to enable upload functionality.',
);
Expand Down
4 changes: 2 additions & 2 deletions packages/models/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ Import the type definitions if using TypeScript:
If you need runtime validation, use the underlying Zod schemas:

```ts
import { coreConfigSchema } from '@code-pushup/models';
import { coreConfigSchema, validate } from '@code-pushup/models';

const json = JSON.parse(readFileSync('code-pushup.config.json'));
const config = coreConfigSchema.parse(json); // throws ZodError if invalid
const config = validate(coreConfigSchema, json); // throws SchemaValidationError if invalid
```
5 changes: 3 additions & 2 deletions packages/models/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
},
"type": "module",
"dependencies": {
"zod": "^4.0.5",
"vscode-material-icons": "^0.1.0"
"ansis": "^3.3.2",
"vscode-material-icons": "^0.1.0",
"zod": "^4.0.5"
},
"files": [
"src",
Expand Down
5 changes: 5 additions & 0 deletions packages/models/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export {
type MaterialIcon,
} from './lib/implementation/schemas.js';
export { exists } from './lib/implementation/utils.js';
export {
SchemaValidationError,
validate,
validateAsync,
} from './lib/implementation/validate.js';
export {
issueSchema,
issueSeveritySchema,
Expand Down
Loading