Skip to content

Commit 8d76297

Browse files
committed
Fixed bugs with the plan process. Fixed invalid validations and refactored logic across plugin, project and ipc-bridge
1 parent e43af3a commit 8d76297

10 files changed

Lines changed: 81 additions & 34 deletions

File tree

codify-core/bin/run.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env node
22

3-
import { execute, handle } from '@oclif/core'
3+
import { flush, handle, run } from '@oclif/core'
44

5-
await execute({ dir: import.meta.url })
6-
.catch(handle)
5+
await run(process.argv.slice(2), import.meta.url)
6+
.catch(async (error) => handle(error))
7+
.finally(async () => flush())

codify-core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"@oclif/plugin-plugins": "^3.8.4",
1010
"semver": "^7.5.4",
1111
"codify-schemas": "1.0.28",
12-
"ajv": "^8.12.0"
12+
"ajv": "^8.12.0",
13+
"ajv-formats": "^3.0.1"
1314
},
1415
"description": "Codify is a set up as code tool for developers",
1516
"devDependencies": {

codify-core/src/commands/plan/orchestrator.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@ import { PluginCollection } from '../../plugins/plugin-collection.js';
44
export const PlanOrchestrator = {
55
async run(rootDirectory: string): Promise<string> {
66
const project = await ConfigCompiler.parseProject(rootDirectory);
7+
if (project.isEmpty()) {
8+
console.log('Empty project. Taking no action');
9+
return '';
10+
}
711

812
const pluginCollection = new PluginCollection();
913
const dependencyMap = await pluginCollection.initialize(project);
1014
project.validateWithResourceMap(dependencyMap);
1115
project.resolveResourceDependencies(dependencyMap);
1216

13-
await pluginCollection.validate(project);
17+
const validationResults = await pluginCollection.validate(project);
18+
project.handlePluginResourceValidationResults(validationResults);
1419
project.calculateEvaluationOrder();
1520

1621
const plan = await pluginCollection.getPlan(project);

codify-core/src/entities/project.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { ValidateResponseData } from 'codify-schemas';
2+
13
import { DependencyMap } from '../plugins/plugin-collection.js';
24
import { DependencyGraphResolver } from '../utils/dependency-graph-resolver.js';
35
import { ProjectConfig } from './project-config.js';
@@ -14,10 +16,16 @@ export class Project {
1416
this.resourceConfigs = resourceConfigs;
1517
}
1618

19+
isEmpty(): boolean {
20+
return this.resourceConfigs.length === 0;
21+
}
22+
1723
validateWithResourceMap(resourceMap: Map<string, string[]>) {
18-
const invalidConfigs = this.resourceConfigs.filter((c) => resourceMap.get(c.type));
24+
const invalidConfigs = this.resourceConfigs.filter((c) => !resourceMap.get(c.type));
1925
if (invalidConfigs.length > 0) {
20-
throw new Error(`Unknown types specified: ${JSON.stringify(invalidConfigs, null, 2)}`);
26+
const invalidTypes = invalidConfigs.map((c) => c.type)
27+
28+
throw new Error(`Unknown type specified: ${invalidTypes.join(',\n')}`);
2129
}
2230
}
2331

@@ -30,6 +38,19 @@ export class Project {
3038
})
3139
}
3240

41+
handlePluginResourceValidationResults(results: ValidateResponseData[]) {
42+
const isValid = results.find((r) => !r.isValid);
43+
if (!isValid) {
44+
const errors = results
45+
.filter((r) => (r.errors?.length ?? 0) > 0)
46+
.flat(1)
47+
.map((e) => JSON.stringify(e, null, 2))
48+
.join('\n\n');
49+
50+
throw new Error(`Config definition errors: \n ${errors}`);
51+
}
52+
}
53+
3354
calculateEvaluationOrder() {
3455
this.evaluationOrder = DependencyGraphResolver.calculateDependencyList(
3556
this.resourceConfigs,

codify-core/src/plugins/entities/plugin.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ import {
22
InitializeResponseData,
33
InitializeResponseDataSchema,
44
PlanResponseData,
5-
PlanResponseDataSchema
5+
PlanResponseDataSchema,
6+
ValidateResponseData,
7+
ValidateResponseDataSchema
68
} from 'codify-schemas';
79

8-
import { ConfigBlock } from '../../entities/index.js';
910
import { ResourceConfig } from '../../entities/resource-config.js';
1011
import { ajv } from '../../utils/ajv.js';
1112
import { PluginIpcBridge } from '../ipc-bridge.js';
1213

1314
const initializeResponseValidator = ajv.compile(InitializeResponseDataSchema);
15+
const validateResponseValidator = ajv.compile(ValidateResponseDataSchema);
1416
const planResponseValidator = ajv.compile(PlanResponseDataSchema);
1517

1618
export class Plugin {
@@ -26,9 +28,10 @@ export class Plugin {
2628
this.path = path;
2729
}
2830

29-
async initialize(ipcBridge?: PluginIpcBridge): Promise<InitializeResponseData> {
30-
ipcBridge = ipcBridge ?? await PluginIpcBridge.create(this.path);
31-
const initializeResponse = await ipcBridge.sendMessageForResult({ cmd: 'initialize' });
31+
async initialize(): Promise<InitializeResponseData> {
32+
this.ipcBridge = await PluginIpcBridge.create(this.path);
33+
34+
const initializeResponse = await this.ipcBridge.sendMessageForResult({ cmd: 'initialize', data: {} });
3235

3336
if (!this.validateInitializeResponse(initializeResponse)) {
3437
throw new Error(`Invalid initialize response from plugin: ${this.name}`);
@@ -41,9 +44,15 @@ export class Plugin {
4144
return initializeResponse;
4245
}
4346

44-
async validate(configs: ConfigBlock[]): Promise<string[]> {
45-
const response = await this.ipcBridge!.sendMessageForResult({ cmd: 'validate', data: { configs } });
46-
return response as string[];
47+
async validate(configs: ResourceConfig[]): Promise<ValidateResponseData> {
48+
const rawConfigs = configs.map((c) => c.raw);
49+
const response = await this.ipcBridge!.sendMessageForResult({ cmd: 'validate', data: { configs: rawConfigs } });
50+
51+
if (!this.validateValidateResponse(response)) {
52+
throw new Error(`Invalid validate response from plugin: ${this.name}`);
53+
}
54+
55+
return response;
4756
}
4857

4958
async plan(resource: ResourceConfig): Promise<PlanResponseData> {
@@ -61,15 +70,23 @@ export class Plugin {
6170
}
6271

6372
private validateInitializeResponse(response: unknown): response is InitializeResponseData {
64-
if (initializeResponseValidator(response)) {
73+
if (!initializeResponseValidator(response)) {
6574
throw new Error(`Invalid initialize response from plugin: ${this.name}. Error: ${initializeResponseValidator.errors}`)
6675
}
6776

6877
return true;
6978
}
7079

80+
private validateValidateResponse(response: unknown): response is ValidateResponseData {
81+
if (!validateResponseValidator(response)) {
82+
throw new Error(`Invalid validate response from plugin: ${this.name}. Error: ${initializeResponseValidator.errors}`)
83+
}
84+
85+
return true;
86+
}
87+
7188
private validatePlanResponse(response: unknown): response is PlanResponseData {
72-
if (planResponseValidator(response)) {
89+
if (!planResponseValidator(response)) {
7390
throw new Error(`Invalid plan response from plugin: ${this.name}. Error: ${initializeResponseValidator.errors}`)
7491
}
7592

codify-core/src/plugins/ipc-bridge.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import { ChildProcess, fork } from 'node:child_process';
2-
import { PluginMessage } from './entities/message.js';
31
import { IpcMessage, IpcMessageSchema } from 'codify-schemas';
2+
import { ChildProcess, fork } from 'node:child_process';
3+
44
import { ajv } from '../utils/ajv.js';
5+
import { PluginMessage } from './entities/message.js';
56

67
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
78

89
type Resolve = (value: unknown) => void;
910
type Reject = (reason?: Error) => void;
1011

11-
const resultFunctionName = (cmd: string) => `${cmd}_Result`;
12+
const resultFunctionName = (cmd: string) => `${cmd}_Response`;
1213

1314
export class PluginIpcBridge {
1415
process: ChildProcess;
@@ -66,7 +67,7 @@ class SendMessageForResultHandler {
6667
}
6768

6869
messageListener = (incomingMessage: unknown) => {
69-
console.log(incomingMessage);
70+
console.log(JSON.stringify(incomingMessage, null, 2));
7071

7172
if (!this.validateIpcMessage(incomingMessage)) {
7273
return this.reject(new Error(`Bad message from plugin. ${JSON.stringify(incomingMessage, null, 2)}`))

codify-core/src/plugins/plugin-collection.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PlanResponseData } from 'codify-schemas';
1+
import { PlanResponseData, ValidateResponseData } from 'codify-schemas';
22

33
import { Project } from '../entities/project.js';
44
import { groupBy } from '../utils/index.js';
@@ -30,23 +30,18 @@ export class PluginCollection {
3030
return dependencyMap;
3131
}
3232

33-
async validate(project: Project): Promise<void> {
33+
async validate(project: Project): Promise<ValidateResponseData[]> {
3434
const { resourceConfigs } = project;
3535
const pluginGroupedResourceConfigs = groupBy(
3636
resourceConfigs,
3737
(item) => this.resourceToPluginMapping.get(item.type)!
3838
);
3939

40-
const result = await Promise.all(
40+
return Promise.all(
4141
Object.entries(pluginGroupedResourceConfigs).map(([pluginName, configs]) =>
4242
this.plugins.get(pluginName)!.validate(configs)
4343
)
4444
);
45-
46-
const errorMessages = result.flat()
47-
if (errorMessages.length > 0) {
48-
throw new Error(`Config validation errors: ${JSON.stringify(errorMessages, null, 2)}`);
49-
}
5045
}
5146

5247
async getPlan(project: Project): Promise<PlanResponseData[]> {

codify-core/src/plugins/resolver.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@ export class PluginResolver {
1616

1717
// TODO: Add plugin versioning in the future
1818
return this.resolvePlugin(name)
19-
20-
throw new Error(`Unable to resolve plugin of name: ${name} and version: ${version}`)
2119
}
2220

2321
private static async resolvePlugin(name: string): Promise<Plugin> {
24-
2522
const { body } = await fetch(DEFAULT_PLUGIN_URL)
2623
if (!body) {
2724
throw new Error(`Un-able to fetch plugin ${name}. Body was null`);

codify-core/src/utils/ajv.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import Ajv2020 from 'ajv/dist/2020.js';
2+
import addFormats from 'ajv-formats';
23

3-
export const ajv = new Ajv2020.default({
4+
const ajv = new Ajv2020.default({
45
allErrors: true,
56
strict: true,
67
});
8+
9+
addFormats.default(ajv);
10+
11+
export { ajv };

codify-core/src/utils/dependency-graph-resolver.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export class DependencyGraphResolver {
1919
* @return a dependency graph in the form of an adjacency list
2020
*/
2121
static calculateDependencyList<T>(vals: T[], getId: (t: T) => string, getDependencyIds: (t: T) => string[]): T[] {
22+
if (vals.length === 0) {
23+
return [];
24+
}
25+
2226
const nodes = vals.map((r) => new Node(getId(r), r));
2327
const nodeMap = new Map(nodes.map((n) => [n.id, n] as const));
2428

@@ -27,7 +31,7 @@ export class DependencyGraphResolver {
2731

2832
const zeroIndegressNodes = nodes.filter((n) => n.indegree === 0);
2933
if (zeroIndegressNodes.length === 0) {
30-
throw new Error('Cyclic dependency found in resource references')
34+
throw new Error('Cyclic dependency found in configs. No resources is found that is not referenced');
3135
}
3236

3337
const queue: Node<T>[] = [];

0 commit comments

Comments
 (0)