Skip to content

Commit d6eb8d4

Browse files
committed
feat: Refactored to share the same plugin error type. Use the reporter to determine how to present it.
1 parent 2ada394 commit d6eb8d4

13 files changed

Lines changed: 116 additions & 75 deletions

package-lock.json

Lines changed: 24 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
},
66
"dependencies": {
77
"@codifycli/ink-form": "0.0.12",
8-
"@codifycli/schemas": "1.1.0-beta7",
8+
"@codifycli/schemas": "1.1.0-beta8",
99
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
1010
"@mischnic/json-sourcemap": "^0.1.1",
1111
"@oclif/core": "^4.0.8",
@@ -43,7 +43,7 @@
4343
},
4444
"description": "Codify is a configuration-as-code tool that declaratively installs and manages developer tools and applications. Check out https://dashboard.codifycli.com for an editor.",
4545
"devDependencies": {
46-
"@codifycli/plugin-core": "^1.1.0-beta13",
46+
"@codifycli/plugin-core": "^1.1.0-beta19",
4747
"@oclif/prettier-config": "^0.2.1",
4848
"@types/chalk": "^2.2.0",
4949
"@types/cors": "^2.8.19",

src/common/base-command.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { DefaultReporter } from '../ui/reporters/default-reporter.js';
1111
import { Reporter, ReporterFactory, ReporterType } from '../ui/reporters/reporter.js';
1212
import { spawnSafe } from '../utils/spawn.js';
1313
import { SudoUtils } from '../utils/sudo.js';
14-
import { PluginApplyValidationError, prettyPrintError } from './errors.js';
14+
import { PluginError, prettyPrintError } from './errors.js';
1515

1616
export abstract class BaseCommand extends Command {
1717
static baseFlags = {
@@ -145,9 +145,9 @@ export abstract class BaseCommand extends Command {
145145
}
146146

147147
protected async catch(err: Error): Promise<void> {
148-
if (err instanceof PluginApplyValidationError && this.reporter) {
148+
if (err instanceof PluginError && this.reporter) {
149149
await this.reporter.hide();
150-
await this.reporter.displayApplyValidationError(err.resourcePlan);
150+
this.reporter.displayPluginError(err);
151151
process.exit(1);
152152
}
153153

src/common/errors.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ErrorObject } from 'ajv';
22
import chalk from 'chalk';
3+
import { PluginErrorData } from '@codifycli/schemas';
34

4-
import { ResourcePlan } from '../entities/plan.js';
55
import { ResourceConfig } from '../entities/resource-config.js';
66
import { SourceMapCache } from '../parser/source-maps.js';
77
import { formatAjvErrors } from '../utils/ajv.js';
@@ -232,13 +232,17 @@ export class SpawnError extends CodifyError {
232232
}
233233
}
234234

235-
export class PluginApplyValidationError extends CodifyError {
236-
name = 'PluginApplyValidationError';
237-
resourcePlan: ResourcePlan;
238-
239-
constructor(resourcePlan: ResourcePlan) {
240-
super(`Apply validation failed for resource: "${resourcePlan.id}".`);
241-
this.resourcePlan = resourcePlan;
235+
export class PluginError extends CodifyError {
236+
name = 'PluginError';
237+
pluginName: string;
238+
resourceType: string;
239+
errorData: PluginErrorData;
240+
241+
constructor(pluginName: string, resourceType: string, errorData: PluginErrorData) {
242+
super(errorData.message);
243+
this.pluginName = pluginName;
244+
this.resourceType = resourceType;
245+
this.errorData = errorData;
242246
}
243247

244248
formattedMessage(): string {

src/plugins/plugin.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
ErrorCode,
2+
ErrorResponseDataSchema,
33
GetResourceInfoResponseData,
44
GetResourceInfoResponseDataSchema,
55
ImportRequestData,
@@ -12,17 +12,19 @@ import {
1212
PlanRequestData,
1313
PlanResponseData,
1414
PlanResponseDataSchema,
15+
PluginErrorData,
1516
ResourceJson,
1617
ValidateResponseData,
1718
ValidateResponseDataSchema,
1819
} from '@codifycli/schemas';
1920

2021
import { ResourcePlan } from '../entities/plan.js';
2122
import { ResourceConfig } from '../entities/resource-config.js';
22-
import { PluginApplyValidationError } from '../common/errors.js';
23+
import { PluginError } from '../common/errors.js';
2324
import { ajv } from '../utils/ajv.js';
2425
import { PluginProcess } from './plugin-process.js';
2526

27+
const errorResponseValidator = ajv.compile(ErrorResponseDataSchema);
2628
const initializeResponseValidator = ajv.compile(InitializeResponseDataSchema);
2729
const validateResponseValidator = ajv.compile(ValidateResponseDataSchema);
2830
const getResourceInfoResponseValidator = ajv.compile(GetResourceInfoResponseDataSchema);
@@ -69,9 +71,9 @@ export class Plugin implements IPlugin {
6971
async validate(configs: ResourceConfig[]): Promise<ValidateResponseData> {
7072
const jsonConfigs = configs.map((c) => c.toJson());
7173
const result = await this.process!.sendMessageForResult('validate', { configs: jsonConfigs });
72-
74+
7375
if (!result.isSuccessful()) {
74-
throw new Error(`Validate error for plugin: "${this.name}" \n\n${JSON.stringify(result.data, null, 2)}`);
76+
throw new PluginError(this.name, 'validate', this.toErrorData(result.data));
7577
}
7678

7779
if (!this.validateValidateResponse(result.data)) {
@@ -85,7 +87,7 @@ export class Plugin implements IPlugin {
8587
const result = await this.process!.sendMessageForResult('getResourceInfo', { type });
8688

8789
if (!result.isSuccessful()) {
88-
throw new Error(`Unable to get info for resource: "${type}" from plugin: "${this.name}" \n\n` + result.data);
90+
throw new PluginError(this.name, type, this.toErrorData(result.data));
8991
}
9092

9193
if (!this.validateGetResourceInfoResponse(result.data)) {
@@ -102,7 +104,7 @@ export class Plugin implements IPlugin {
102104
});
103105

104106
if (!result.isSuccessful()) {
105-
throw new Error(`Unable to match resource: "${resource.type}" from plugin: "${this.name}" \n\n` + result.data);
107+
throw new PluginError(this.name, resource.type, this.toErrorData(result.data));
106108
}
107109

108110
if (!this.validateMatchResponse(result.data)) {
@@ -112,12 +114,11 @@ export class Plugin implements IPlugin {
112114
return result.data;
113115
}
114116

115-
116117
async import(config: ResourceJson, autoSearchAll = false): Promise<ImportResponseData> {
117118
const result = await this.process!.sendMessageForResult('import', <ImportRequestData>{ ...config, autoSearchAll });
118119

119120
if (!result.isSuccessful()) {
120-
throw new Error(`Unable import resource ${config.core.type} with plugin: "${this.name}" \n\n` + result.data);
121+
throw new PluginError(this.name, config.core.type, this.toErrorData(result.data));
121122
}
122123

123124
if (!this.validateImportResponse(result.data)) {
@@ -128,13 +129,10 @@ export class Plugin implements IPlugin {
128129
}
129130

130131
async plan(request: PlanRequestData): Promise<ResourcePlan> {
131-
const result = await this.process!.sendMessageForResult(
132-
'plan',
133-
request
134-
);
132+
const result = await this.process!.sendMessageForResult('plan', request);
135133

136134
if (!result.isSuccessful()) {
137-
throw new Error(`Plan error for plugin: "${this.name}", resource: "${request.core.type}" \n\n` + result.data);
135+
throw new PluginError(this.name, request.core.type, this.toErrorData(result.data));
138136
}
139137

140138
if (!this.validatePlanResponse(result.data)) {
@@ -148,25 +146,23 @@ export class Plugin implements IPlugin {
148146
const result = await this.process!.sendMessageForResult('apply', { plan });
149147

150148
if (!result.isSuccessful()) {
151-
const data = result.data as any;
152-
153-
if (data?.errorCode === ErrorCode.APPLY_VALIDATION && data.plan) {
154-
throw new PluginApplyValidationError(new ResourcePlan(data.plan));
155-
}
156-
157-
const message = typeof data === 'string'
158-
? data
159-
: (data?.message ?? JSON.stringify(data, null, 2));
160-
throw new Error(`Apply error for plugin: "${this.name}", resource: "${plan.resourceType}" \n\n` + message);
149+
throw new PluginError(this.name, plan.resourceType, this.toErrorData(result.data));
161150
}
162151
}
163152

164153
async setVerbosityLevel(verbosityLevel: number): Promise<void> {
165154
const result = await this.process!.sendMessageForResult('setVerbosityLevel', { verbosityLevel });
166155

167156
if (!result.isSuccessful()) {
168-
throw new Error(`Set verbosity error for plugin: "${this.name}" \n\n` + result.data);
157+
throw new PluginError(this.name, 'setVerbosityLevel', this.toErrorData(result.data));
158+
}
159+
}
160+
161+
private toErrorData(data: unknown): PluginErrorData {
162+
if (errorResponseValidator(data)) {
163+
return data as unknown as PluginErrorData;
169164
}
165+
return { errorType: 'unknown', message: typeof data === 'string' ? data : JSON.stringify(data, null, 2) };
170166
}
171167

172168
kill() {

src/ui/components/default-component.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ export function DefaultComponent(props: {
6868
}</Static>
6969
)
7070
}
71+
{
72+
renderStatus === RenderStatus.PLUGIN_ERROR && (
73+
<Static items={[renderData as string]}>{
74+
(message, idx) => (
75+
<Box key={idx} flexDirection="column" marginTop={1}>
76+
<Text color="red">{message}</Text>
77+
</Box>
78+
)
79+
}</Static>
80+
)
81+
}
7182
{
7283
renderStatus === RenderStatus.PROMPT_CONFIRMATION && (
7384
<Box flexDirection="column">

src/ui/plugin-error-formatter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PluginError } from '../common/errors.js';
2+
import { ResourcePlan } from '../entities/plan.js';
3+
import { prettyFormatResourcePlan } from './plan-pretty-printer.js';
4+
5+
export function formatApplyValidationError(error: PluginError): string {
6+
const plan = new ResourcePlan((error.errorData.data as any).plan);
7+
return [
8+
`Apply validation failed: resource "${plan.id}" did not reach its desired state.`,
9+
'Changes still needed:',
10+
prettyFormatResourcePlan(plan),
11+
].join('\n');
12+
}

src/ui/reporters/default-reporter.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import React from 'react';
66
import stripAnsi from 'strip-ansi'
77

88
import { Plan, ResourcePlan } from '../../entities/plan.js';
9+
import { PluginError } from '../../common/errors.js';
910
import { ResourceConfig } from '../../entities/resource-config.js';
1011
import { ResourceInfo } from '../../entities/resource-info.js';
1112
import { ctx, Event, ProcessName, SubProcessName } from '../../events/context.js';
@@ -267,8 +268,13 @@ export class DefaultReporter implements Reporter {
267268
void this.updateRenderState(RenderStatus.DISPLAY_FILE_MODIFICATION, diff);
268269
}
269270

270-
async displayApplyValidationError(resourcePlan: ResourcePlan) {
271-
await this.updateRenderState(RenderStatus.APPLY_VALIDATION_ERROR, resourcePlan);
271+
displayPluginError(error: PluginError): void {
272+
if (error.errorData.errorType === 'apply_validation') {
273+
const resourcePlan = new ResourcePlan((error.errorData.data as any).plan);
274+
void this.updateRenderState(RenderStatus.APPLY_VALIDATION_ERROR, resourcePlan);
275+
return;
276+
}
277+
void this.updateRenderState(RenderStatus.PLUGIN_ERROR, error.message);
272278
}
273279

274280
private log(log: string): void {

src/ui/reporters/json-reporter.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CommandRequestData } from '@codifycli/schemas';
22

3-
import { Plan, ResourcePlan } from '../../entities/plan.js';
3+
import { Plan } from '../../entities/plan.js';
4+
import { PluginError } from '../../common/errors.js';
45
import { ResourceConfig } from '../../entities/resource-config.js';
56
import { ImportResult } from '../../orchestrators/import.js';
67
import { Reporter } from './reporter.js';
@@ -72,12 +73,13 @@ export class JsonReporter implements Reporter {
7273
throw new Error('Json reporter error: disableRawMode is not supported. Raw stdin mode requires interactive terminal access.');
7374
}
7475

75-
displayApplyValidationError(resourcePlan: ResourcePlan): void {
76+
displayPluginError(error: PluginError): void {
7677
console.log(JSON.stringify({
77-
error: 'apply_validation',
78-
resourceId: resourcePlan.id,
79-
operation: resourcePlan.operation,
80-
parameters: resourcePlan.parameters,
78+
errorType: error.errorData.errorType,
79+
message: error.message,
80+
pluginName: error.pluginName,
81+
resourceType: error.resourceType,
82+
data: error.errorData.data,
8183
}, null, 2));
8284
}
8385
}

0 commit comments

Comments
 (0)