Skip to content

Commit b43d527

Browse files
committed
Added initialization code
1 parent 7c00ed6 commit b43d527

8 files changed

Lines changed: 151 additions & 101 deletions

File tree

codify-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"@oclif/plugin-help": "^5",
99
"@oclif/plugin-plugins": "^3.8.4",
1010
"semver": "^7.5.4",
11-
"codify-schemas": "1.0.25",
11+
"codify-schemas": "1.0.28",
1212
"ajv": "^8.12.0"
1313
},
1414
"description": "Codify is a set up as code tool for developers",

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { ConfigCompiler } from '../../config-compiler/index.js';
2+
import { PluginCollection } from '../../plugins/plugin-collection.js';
23

34
export const PlanOrchestrator = {
45
async run(rootDirectory: string): Promise<string> {
56
const parsedProject = await ConfigCompiler.parseProject(rootDirectory);
67

7-
// const pluginCollection = await PluginCollection.create(parsedProject);
8-
// const definitions = await pluginCollection.getResourceDefinitions();
9-
//
8+
const pluginCollection = new PluginCollection();
9+
const resourceMap = await pluginCollection.initialize(parsedProject);
10+
11+
parsedProject.validateWithResourceMap(resourceMap);
12+
13+
1014
// const compiledProject = await ConfigCompiler.compileProject(parsedProject, definitions);
1115
// console.log(compiledProject);
1216
// const plan = await pluginCollection.getPlan(compiledProject);

codify-core/src/config-compiler/parser/entities/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ConfigClass } from '../../language-definition.js';
22

33
export interface ConfigBlock {
4+
type: string;
45
configClass: ConfigClass;
56

67
validateConfig(config: unknown): never | void;

codify-core/src/config-compiler/parser/entities/parsed-project.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,11 @@ export class ParsedProject {
1414
getModuleTree(): ParsedModule {
1515
return this.coreModule;
1616
}
17+
18+
validateWithResourceMap(resourceMap: Map<string, string[]>) {
19+
const invalidConfigs = this.coreModule.configBlocks.filter((c) => resourceMap.get(c.type));
20+
if (invalidConfigs.length > 0) {
21+
throw new Error(`Unknown types specified: ${JSON.stringify(invalidConfigs, null, 2)}`);
22+
}
23+
}
1724
}
Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
11
import { Applyable } from '../../config-compiler/output-generator/entities/index.js';
22
import { PluginIpcBridge } from '../ipc-bridge.js';
3-
import { PluginData } from './plugin-data.js';
3+
import { InitializeResponseData, InitializeResponseDataSchema } from 'codify-schemas';
4+
import { ajv } from '../../utils/ajv.js';
5+
6+
const initializeResponseValidator = ajv.compile(InitializeResponseDataSchema);
47

58
export class Plugin {
69

710
ipcBridge?: PluginIpcBridge;
811

9-
// Separate out data so that the validation logic is testable.
10-
data: PluginData;
12+
name: string;
13+
path: string;
14+
resourceDependenciesMap = new Map<string, string[]>()
1115

12-
constructor(data: PluginData) {
13-
this.data = data;
16+
constructor(name: string, path: string) {
17+
this.name = name;
18+
this.path = path;
1419
}
1520

16-
async initialize(ipcBridge?: PluginIpcBridge): Promise<unknown> {
17-
ipcBridge = ipcBridge ?? await PluginIpcBridge.create(this.data.directory);
18-
const resourceList = await ipcBridge.sendMessageForResult({ cmd: 'initialize' });
21+
async initialize(ipcBridge?: PluginIpcBridge): Promise<InitializeResponseData> {
22+
ipcBridge = ipcBridge ?? await PluginIpcBridge.create(this.path);
23+
const initializeResponse = await ipcBridge.sendMessageForResult({ cmd: 'initialize' });
24+
25+
if (!this.validateInitializeResponse(initializeResponse)) {
26+
throw new Error();
27+
}
28+
29+
initializeResponse.resourceDefinitions.forEach((d) => {
30+
this.resourceDependenciesMap.set(d.type, d.dependencies)
31+
});
1932

20-
this.data.resourceDefinitions = resourceList as any;
21-
return resourceList;
33+
return initializeResponse;
2234
}
2335

2436
async generateResourcePlan(applyable: Applyable): Promise<unknown> {
@@ -28,4 +40,12 @@ export class Plugin {
2840
destroy() {
2941
this.ipcBridge!.killPlugin();
3042
}
43+
44+
private validateInitializeResponse(response: unknown): response is InitializeResponseData {
45+
if (initializeResponseValidator(response)) {
46+
throw new Error(`Invalid initialize response from plugin: ${this.name}. Error: ${initializeResponseValidator.errors}`)
47+
}
48+
49+
return true;
50+
}
3151
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { ChildProcess, fork } from 'node:child_process';
2-
import { validateTypeRecordStringUnknown } from '../utils/validator.js';
32
import { PluginMessage } from './entities/message.js';
3+
import { IpcMessage, IpcMessageSchema } from 'codify-schemas';
4+
import { ajv } from '../utils/ajv.js';
5+
6+
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
47

58
type Resolve = (value: unknown) => void;
69
type Reject = (reason?: Error) => void;
@@ -65,7 +68,7 @@ class SendMessageForResultHandler {
6568
messageListener = (incomingMessage: unknown) => {
6669
console.log(incomingMessage);
6770

68-
if (!validateTypeRecordStringUnknown(incomingMessage)) {
71+
if (!this.validateIpcMessage(incomingMessage)) {
6972
return this.reject(new Error(`Bad message from plugin. ${JSON.stringify(incomingMessage, null, 2)}`))
7073
}
7174

@@ -97,5 +100,9 @@ class SendMessageForResultHandler {
97100
private setResultTimeout = () => setTimeout(() => {
98101
this.reject(new Error(`Plugin did not respond in 10s to call: ${this.messageToSend.cmd}`))
99102
}, 10_000);
103+
104+
private validateIpcMessage(response: unknown): response is IpcMessage {
105+
return ipcMessageValidator(response);
106+
}
100107
}
101108

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { CompiledProject } from '../config-compiler/output-generator/entities/compiled-project.js';
2+
import { ParsedProject } from '../config-compiler/parser/entities/parsed-project.js';
3+
import { Plugin } from './entities/plugin.js';
4+
import { PluginResolver } from './resolver.js';
5+
6+
type PluginName = string;
7+
8+
const DEFAULT_PLUGINS = {
9+
'default': 'latest',
10+
// 'default:node': 'latest',
11+
}
12+
13+
export class PluginCollection {
14+
15+
private plugins = new Map<PluginName, Plugin>()
16+
private resourceToPluginMapping = new Map<string, string>()
17+
private pluginToResourceMapping = new Map<string, string[]>()
18+
19+
async initialize(project: ParsedProject): Promise<Map<string, string[]>> {
20+
const plugins = await this.resolvePlugins(project);
21+
22+
plugins.forEach((plugin) => {
23+
this.plugins.set(plugin.name, plugin)
24+
})
25+
26+
const dependencyMap = await this.initializePlugins(plugins);
27+
return dependencyMap;
28+
}
29+
30+
async getPlan(project: CompiledProject): Promise<Array<string>> {
31+
const result = new Array<string>();
32+
for (const applyable of project.getApplySequence()) {
33+
const plugin = this.plugins.get(applyable.pluginName);
34+
if (!plugin) {
35+
continue;
36+
}
37+
38+
console.log('a');
39+
40+
// eslint-disable-next-line no-await-in-loop
41+
result.push(await plugin.generateResourcePlan(applyable) as string);
42+
}
43+
44+
return result;
45+
}
46+
47+
async destroy(): Promise<void> {
48+
for (const plugin of this.plugins.values()) {
49+
plugin.destroy();
50+
}
51+
}
52+
53+
private async resolvePlugins(project: ParsedProject): Promise<Plugin[]> {
54+
const pluginDefinitions: Record<string, string> = {
55+
...DEFAULT_PLUGINS,
56+
...project.projectConfig.plugins,
57+
};
58+
59+
return await Promise.all(Object.entries(pluginDefinitions).map(([name, version]) =>
60+
PluginResolver.resolve(name, version)
61+
));
62+
}
63+
64+
private async initializePlugins(plugins: Plugin[]): Promise<Map<string, string[]>> {
65+
const responses = await Promise.all(
66+
plugins.map(async (p) => [p.name, (await p.initialize()).resourceDefinitions] as const)
67+
);
68+
69+
const resourceMap = new Map<string, string[]>;
70+
71+
for (const [pluginName, definitions] of responses) {
72+
for (const definition of definitions) {
73+
// Build resource to plugin mapping
74+
if (this.resourceToPluginMapping.has(definition.type)) {
75+
throw new Error(`Duplicated types between plugin ${this.resourceToPluginMapping.get(definition.type)} and ${pluginName}`)
76+
}
77+
this.resourceToPluginMapping.set(definition.type, pluginName);
78+
79+
// Build plugin to resource mapping
80+
if (!this.pluginToResourceMapping.has(pluginName)) {
81+
this.pluginToResourceMapping.set(pluginName, []);
82+
}
83+
this.pluginToResourceMapping.get(pluginName)!.push(definition.type);
84+
85+
// Build resource dependency map
86+
if (resourceMap.has(definition.type)) {
87+
throw new Error(`Duplicated types between plugins ${this.resourceToPluginMapping.get(definition.type)} and ${pluginName}`);
88+
}
89+
resourceMap.set(definition.type, definition.dependencies)
90+
}
91+
}
92+
93+
return resourceMap;
94+
}
95+
96+
}

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

Lines changed: 0 additions & 85 deletions
This file was deleted.

0 commit comments

Comments
 (0)