Skip to content

Commit 2ada394

Browse files
committed
feat: Improved apply validation errors
1 parent 9eab689 commit 2ada394

14 files changed

Lines changed: 95 additions & 20 deletions

File tree

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
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-beta5",
8+
"@codifycli/schemas": "1.1.0-beta7",
99
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
1010
"@mischnic/json-sourcemap": "^0.1.1",
1111
"@oclif/core": "^4.0.8",

src/common/base-command.ts

Lines changed: 7 additions & 1 deletion
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 { prettyPrintError } from './errors.js';
14+
import { PluginApplyValidationError, prettyPrintError } from './errors.js';
1515

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

147147
protected async catch(err: Error): Promise<void> {
148+
if (err instanceof PluginApplyValidationError && this.reporter) {
149+
await this.reporter.hide();
150+
await this.reporter.displayApplyValidationError(err.resourcePlan);
151+
process.exit(1);
152+
}
153+
148154
prettyPrintError(err);
149155
process.exit(1);
150156
}

src/common/errors.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ErrorObject } from 'ajv';
22
import chalk from 'chalk';
33

4+
import { ResourcePlan } from '../entities/plan.js';
45
import { ResourceConfig } from '../entities/resource-config.js';
56
import { SourceMapCache } from '../parser/source-maps.js';
67
import { formatAjvErrors } from '../utils/ajv.js';
@@ -231,6 +232,20 @@ export class SpawnError extends CodifyError {
231232
}
232233
}
233234

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;
242+
}
243+
244+
formattedMessage(): string {
245+
return this.message;
246+
}
247+
}
248+
234249
export function prettyPrintError(error: unknown): void {
235250
if (error instanceof CodifyError) {
236251
return console.error(chalk.red(error.formattedMessage()));

src/orchestrators/apply.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const ApplyOrchestrator = {
5050
// Need to sleep to wait for the message to display before we exit
5151
await sleep(100);
5252

53-
reporter.displayMessage(`
53+
await reporter.displayMessage(`
5454
🎉 Finished applying 🎉
5555
Open a new terminal or source '.zshrc' for the new changes to be reflected`);
5656
},

src/orchestrators/import.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ export class ImportOrchestrator {
195195

196196
// No writes
197197
reporter.displayImportResult(importResult, true);
198-
reporter.displayMessage('\n🎉 Imported completed 🎉')
198+
await reporter.displayMessage('\n🎉 Imported completed 🎉')
199199

200200
await sleep(100);
201201
}

src/plugins/plugin.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ErrorCode,
23
GetResourceInfoResponseData,
34
GetResourceInfoResponseDataSchema,
45
ImportRequestData,
@@ -18,6 +19,7 @@ import {
1819

1920
import { ResourcePlan } from '../entities/plan.js';
2021
import { ResourceConfig } from '../entities/resource-config.js';
22+
import { PluginApplyValidationError } from '../common/errors.js';
2123
import { ajv } from '../utils/ajv.js';
2224
import { PluginProcess } from './plugin-process.js';
2325

@@ -146,7 +148,16 @@ export class Plugin implements IPlugin {
146148
const result = await this.process!.sendMessageForResult('apply', { plan });
147149

148150
if (!result.isSuccessful()) {
149-
throw new Error(`Apply error for plugin: "${this.name}", resource: "${plan.resourceType}" \n\n` + result.data);
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);
150161
}
151162
}
152163

src/ui/components/default-component.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { useAtom } from 'jotai';
55
import { EventEmitter } from 'node:events';
66
import React, { useLayoutEffect } from 'react';
77

8-
import { Plan } from '../../entities/plan.js';
8+
import { Plan, ResourcePlan } from '../../entities/plan.js';
9+
import { prettyFormatResourcePlan } from '../plan-pretty-printer.js';
910
import { FileModificationResult } from '../../generators/index.js';
1011
import { ImportResult } from '../../orchestrators/import.js';
1112
import { RenderEvent } from '../reporters/reporter.js';
@@ -49,6 +50,24 @@ export function DefaultComponent(props: {
4950
(plan, idx) => <PlanComponent key={idx} plan={plan}/>
5051
}</Static>
5152
}
53+
{
54+
renderStatus === RenderStatus.APPLY_VALIDATION_ERROR && (
55+
<Static items={[renderData as ResourcePlan]}>{
56+
(resourcePlan, idx) => (
57+
<Box key={idx} flexDirection="column" marginTop={1}>
58+
<Text color="red" bold>
59+
{`Apply validation failed: resource "${resourcePlan.id}" did not reach its desired state. \nExiting...`}
60+
</Text>
61+
<Text> </Text>
62+
<Text bold backgroundColor={'red'}>Changes still needed:</Text>
63+
<Text>{prettyFormatResourcePlan(resourcePlan)}</Text>
64+
<Text> </Text>
65+
<Text>Try re-running to see if the changes have applied.</Text>
66+
</Box>
67+
)
68+
}</Static>
69+
)
70+
}
5271
{
5372
renderStatus === RenderStatus.PROMPT_CONFIRMATION && (
5473
<Box flexDirection="column">

src/ui/reporters/default-reporter.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { EventEmitter } from 'node:events';
55
import React from 'react';
66
import stripAnsi from 'strip-ansi'
77

8-
import { Plan } from '../../entities/plan.js';
8+
import { Plan, ResourcePlan } from '../../entities/plan.js';
99
import { ResourceConfig } from '../../entities/resource-config.js';
1010
import { ResourceInfo } from '../../entities/resource-info.js';
1111
import { ctx, Event, ProcessName, SubProcessName } from '../../events/context.js';
@@ -226,8 +226,8 @@ export class DefaultReporter implements Reporter {
226226
void this.updateRenderState(RenderStatus.DISPLAY_PLAN, plan)
227227
}
228228

229-
displayMessage(message: string) {
230-
void this.updateRenderState(RenderStatus.DISPLAY_MESSAGE, message);
229+
async displayMessage(message: string) {
230+
await this.updateRenderState(RenderStatus.DISPLAY_MESSAGE, message);
231231
}
232232

233233
async promptInitResultSelection(availableTypes: string[]): Promise<string[]> {
@@ -267,6 +267,10 @@ export class DefaultReporter implements Reporter {
267267
void this.updateRenderState(RenderStatus.DISPLAY_FILE_MODIFICATION, diff);
268268
}
269269

270+
async displayApplyValidationError(resourcePlan: ResourcePlan) {
271+
await this.updateRenderState(RenderStatus.APPLY_VALIDATION_ERROR, resourcePlan);
272+
}
273+
270274
private log(log: string): void {
271275
if (this.silent) return;
272276

src/ui/reporters/json-reporter.ts

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

3-
import { Plan } from '../../entities/plan.js';
3+
import { Plan, ResourcePlan } from '../../entities/plan.js';
44
import { ResourceConfig } from '../../entities/resource-config.js';
55
import { ImportResult } from '../../orchestrators/import.js';
66
import { Reporter } from './reporter.js';
@@ -71,4 +71,13 @@ export class JsonReporter implements Reporter {
7171
async disableRawMode(): Promise<void> {
7272
throw new Error('Json reporter error: disableRawMode is not supported. Raw stdin mode requires interactive terminal access.');
7373
}
74+
75+
displayApplyValidationError(resourcePlan: ResourcePlan): void {
76+
console.log(JSON.stringify({
77+
error: 'apply_validation',
78+
resourceId: resourcePlan.id,
79+
operation: resourcePlan.operation,
80+
parameters: resourcePlan.parameters,
81+
}, null, 2));
82+
}
7483
}

0 commit comments

Comments
 (0)