@codifycli/plugin-core 1.0.0-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.json +30 -0
  3. package/.github/workflows/release.yaml +19 -0
  4. package/.github/workflows/unit-test-ci.yaml +18 -0
  5. package/.prettierrc.json +1 -0
  6. package/bin/build.js +189 -0
  7. package/dist/bin/build.d.ts +1 -0
  8. package/dist/bin/build.js +80 -0
  9. package/dist/bin/deploy-plugin.d.ts +2 -0
  10. package/dist/bin/deploy-plugin.js +8 -0
  11. package/dist/common/errors.d.ts +8 -0
  12. package/dist/common/errors.js +24 -0
  13. package/dist/entities/change-set.d.ts +24 -0
  14. package/dist/entities/change-set.js +152 -0
  15. package/dist/entities/errors.d.ts +4 -0
  16. package/dist/entities/errors.js +7 -0
  17. package/dist/entities/plan-types.d.ts +25 -0
  18. package/dist/entities/plan-types.js +1 -0
  19. package/dist/entities/plan.d.ts +15 -0
  20. package/dist/entities/plan.js +127 -0
  21. package/dist/entities/plugin.d.ts +16 -0
  22. package/dist/entities/plugin.js +80 -0
  23. package/dist/entities/resource-options.d.ts +31 -0
  24. package/dist/entities/resource-options.js +76 -0
  25. package/dist/entities/resource-types.d.ts +11 -0
  26. package/dist/entities/resource-types.js +1 -0
  27. package/dist/entities/resource.d.ts +42 -0
  28. package/dist/entities/resource.js +303 -0
  29. package/dist/entities/stateful-parameter.d.ts +29 -0
  30. package/dist/entities/stateful-parameter.js +46 -0
  31. package/dist/entities/transform-parameter.d.ts +4 -0
  32. package/dist/entities/transform-parameter.js +2 -0
  33. package/dist/errors.d.ts +4 -0
  34. package/dist/errors.js +7 -0
  35. package/dist/index.d.ts +20 -0
  36. package/dist/index.js +26 -0
  37. package/dist/messages/handlers.d.ts +14 -0
  38. package/dist/messages/handlers.js +134 -0
  39. package/dist/messages/sender.d.ts +11 -0
  40. package/dist/messages/sender.js +57 -0
  41. package/dist/plan/change-set.d.ts +53 -0
  42. package/dist/plan/change-set.js +153 -0
  43. package/dist/plan/plan-types.d.ts +23 -0
  44. package/dist/plan/plan-types.js +1 -0
  45. package/dist/plan/plan.d.ts +66 -0
  46. package/dist/plan/plan.js +328 -0
  47. package/dist/plugin/plugin.d.ts +24 -0
  48. package/dist/plugin/plugin.js +200 -0
  49. package/dist/pty/background-pty.d.ts +21 -0
  50. package/dist/pty/background-pty.js +127 -0
  51. package/dist/pty/index.d.ts +50 -0
  52. package/dist/pty/index.js +20 -0
  53. package/dist/pty/promise-queue.d.ts +5 -0
  54. package/dist/pty/promise-queue.js +26 -0
  55. package/dist/pty/seqeuntial-pty.d.ts +17 -0
  56. package/dist/pty/seqeuntial-pty.js +119 -0
  57. package/dist/pty/vitest.config.d.ts +2 -0
  58. package/dist/pty/vitest.config.js +11 -0
  59. package/dist/resource/config-parser.d.ts +11 -0
  60. package/dist/resource/config-parser.js +21 -0
  61. package/dist/resource/parsed-resource-settings.d.ts +47 -0
  62. package/dist/resource/parsed-resource-settings.js +196 -0
  63. package/dist/resource/resource-controller.d.ts +36 -0
  64. package/dist/resource/resource-controller.js +402 -0
  65. package/dist/resource/resource-settings.d.ts +303 -0
  66. package/dist/resource/resource-settings.js +147 -0
  67. package/dist/resource/resource.d.ts +144 -0
  68. package/dist/resource/resource.js +44 -0
  69. package/dist/resource/stateful-parameter.d.ts +165 -0
  70. package/dist/resource/stateful-parameter.js +94 -0
  71. package/dist/scripts/deploy.d.ts +1 -0
  72. package/dist/scripts/deploy.js +2 -0
  73. package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
  74. package/dist/stateful-parameter/stateful-parameter-controller.js +81 -0
  75. package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
  76. package/dist/stateful-parameter/stateful-parameter.js +43 -0
  77. package/dist/test.d.ts +1 -0
  78. package/dist/test.js +5 -0
  79. package/dist/utils/codify-spawn.d.ts +29 -0
  80. package/dist/utils/codify-spawn.js +136 -0
  81. package/dist/utils/debug.d.ts +2 -0
  82. package/dist/utils/debug.js +10 -0
  83. package/dist/utils/file-utils.d.ts +23 -0
  84. package/dist/utils/file-utils.js +186 -0
  85. package/dist/utils/functions.d.ts +12 -0
  86. package/dist/utils/functions.js +74 -0
  87. package/dist/utils/index.d.ts +46 -0
  88. package/dist/utils/index.js +271 -0
  89. package/dist/utils/internal-utils.d.ts +12 -0
  90. package/dist/utils/internal-utils.js +74 -0
  91. package/dist/utils/load-resources.d.ts +1 -0
  92. package/dist/utils/load-resources.js +46 -0
  93. package/dist/utils/package-json-utils.d.ts +12 -0
  94. package/dist/utils/package-json-utils.js +34 -0
  95. package/dist/utils/pty-local-storage.d.ts +2 -0
  96. package/dist/utils/pty-local-storage.js +2 -0
  97. package/dist/utils/spawn-2.d.ts +5 -0
  98. package/dist/utils/spawn-2.js +7 -0
  99. package/dist/utils/spawn.d.ts +29 -0
  100. package/dist/utils/spawn.js +124 -0
  101. package/dist/utils/utils.d.ts +18 -0
  102. package/dist/utils/utils.js +86 -0
  103. package/dist/utils/verbosity-level.d.ts +5 -0
  104. package/dist/utils/verbosity-level.js +9 -0
  105. package/package.json +59 -0
  106. package/rollup.config.js +24 -0
  107. package/src/common/errors.test.ts +43 -0
  108. package/src/common/errors.ts +31 -0
  109. package/src/errors.ts +8 -0
  110. package/src/index.test.ts +6 -0
  111. package/src/index.ts +30 -0
  112. package/src/messages/handlers.test.ts +329 -0
  113. package/src/messages/handlers.ts +181 -0
  114. package/src/messages/sender.ts +69 -0
  115. package/src/plan/change-set.test.ts +280 -0
  116. package/src/plan/change-set.ts +236 -0
  117. package/src/plan/plan-types.ts +27 -0
  118. package/src/plan/plan.test.ts +413 -0
  119. package/src/plan/plan.ts +499 -0
  120. package/src/plugin/plugin.test.ts +533 -0
  121. package/src/plugin/plugin.ts +291 -0
  122. package/src/pty/background-pty.test.ts +69 -0
  123. package/src/pty/background-pty.ts +154 -0
  124. package/src/pty/index.test.ts +129 -0
  125. package/src/pty/index.ts +66 -0
  126. package/src/pty/promise-queue.ts +33 -0
  127. package/src/pty/seqeuntial-pty.ts +151 -0
  128. package/src/pty/sequential-pty.test.ts +194 -0
  129. package/src/resource/config-parser.ts +42 -0
  130. package/src/resource/parsed-resource-settings.test.ts +186 -0
  131. package/src/resource/parsed-resource-settings.ts +307 -0
  132. package/src/resource/resource-controller-stateful-mode.test.ts +253 -0
  133. package/src/resource/resource-controller.test.ts +1081 -0
  134. package/src/resource/resource-controller.ts +563 -0
  135. package/src/resource/resource-settings.test.ts +1213 -0
  136. package/src/resource/resource-settings.ts +545 -0
  137. package/src/resource/resource.ts +157 -0
  138. package/src/stateful-parameter/stateful-parameter-controller.test.ts +244 -0
  139. package/src/stateful-parameter/stateful-parameter-controller.ts +111 -0
  140. package/src/stateful-parameter/stateful-parameter.ts +160 -0
  141. package/src/utils/debug.ts +11 -0
  142. package/src/utils/file-utils.test.ts +7 -0
  143. package/src/utils/file-utils.ts +231 -0
  144. package/src/utils/functions.ts +103 -0
  145. package/src/utils/index.ts +340 -0
  146. package/src/utils/internal-utils.test.ts +52 -0
  147. package/src/utils/pty-local-storage.ts +3 -0
  148. package/src/utils/test-utils.test.ts +96 -0
  149. package/src/utils/verbosity-level.ts +11 -0
  150. package/tsconfig.json +26 -0
  151. package/tsconfig.test.json +9 -0
  152. package/vitest.config.ts +10 -0
@@ -0,0 +1,303 @@
1
+ import { Ajv } from 'ajv';
2
+ import { ParameterOperation, ResourceOperation, } from 'codify-schemas';
3
+ import { setsEqual, splitUserConfig } from '../utils/utils.js';
4
+ import { Plan } from './plan.js';
5
+ import { ResourceOptionsParser } from './resource-options.js';
6
+ export class Resource {
7
+ typeId;
8
+ statefulParameters;
9
+ transformParameters;
10
+ resourceParameters;
11
+ statefulParameterOrder;
12
+ transformParameterOrder;
13
+ dependencies;
14
+ parameterOptions;
15
+ options;
16
+ defaultValues;
17
+ ajv;
18
+ schemaValidator;
19
+ constructor(options) {
20
+ this.typeId = options.type;
21
+ this.dependencies = options.dependencies ?? [];
22
+ this.options = options;
23
+ if (this.options.schema) {
24
+ this.ajv = new Ajv({
25
+ allErrors: true,
26
+ strict: true,
27
+ strictRequired: false,
28
+ });
29
+ this.schemaValidator = this.ajv.compile(this.options.schema);
30
+ }
31
+ const parser = new ResourceOptionsParser(options);
32
+ this.statefulParameters = parser.statefulParameters;
33
+ this.transformParameters = parser.transformParameters;
34
+ this.resourceParameters = parser.resourceParameters;
35
+ this.parameterOptions = parser.changeSetParameterOptions;
36
+ this.defaultValues = parser.defaultValues;
37
+ this.statefulParameterOrder = parser.statefulParameterOrder;
38
+ this.transformParameterOrder = parser.transformParameterOrder;
39
+ }
40
+ async onInitialize() { }
41
+ async validate(parameters, resourceMetaData) {
42
+ if (this.schemaValidator) {
43
+ const isValid = this.schemaValidator(parameters);
44
+ if (!isValid) {
45
+ return {
46
+ isValid: false,
47
+ resourceName: resourceMetaData.name,
48
+ resourceType: resourceMetaData.type,
49
+ schemaValidationErrors: this.schemaValidator?.errors ?? [],
50
+ };
51
+ }
52
+ }
53
+ let isValid = true;
54
+ let customValidationErrorMessage;
55
+ try {
56
+ await this.customValidation(parameters);
57
+ }
58
+ catch (error) {
59
+ isValid = false;
60
+ customValidationErrorMessage = error.message;
61
+ }
62
+ if (!isValid) {
63
+ return {
64
+ customValidationErrorMessage,
65
+ isValid: false,
66
+ resourceName: resourceMetaData.name,
67
+ resourceType: resourceMetaData.type,
68
+ schemaValidationErrors: this.schemaValidator?.errors ?? [],
69
+ };
70
+ }
71
+ return {
72
+ isValid: true,
73
+ resourceName: resourceMetaData.name,
74
+ resourceType: resourceMetaData.type,
75
+ schemaValidationErrors: [],
76
+ };
77
+ }
78
+ async plan(desiredConfig, currentConfig = null, statefulMode = false) {
79
+ this.validatePlanInputs(desiredConfig, currentConfig, statefulMode);
80
+ const planOptions = {
81
+ parameterOptions: this.parameterOptions,
82
+ statefulMode,
83
+ };
84
+ this.addDefaultValues(desiredConfig);
85
+ await this.applyTransformParameters(desiredConfig);
86
+ const parsedConfig = new ConfigParser(desiredConfig, currentConfig, this.statefulParameters, this.transformParameters);
87
+ const { desiredParameters, nonStatefulParameters, resourceMetadata, statefulParameters, } = parsedConfig;
88
+ const currentParameters = await this.refreshNonStatefulParameters(nonStatefulParameters);
89
+ if (currentParameters === null || currentParameters === undefined) {
90
+ return Plan.create(desiredParameters, null, resourceMetadata, planOptions);
91
+ }
92
+ const statefulCurrentParameters = await this.refreshStatefulParameters(statefulParameters, planOptions.statefulMode);
93
+ return Plan.create(desiredParameters, { ...currentParameters, ...statefulCurrentParameters }, resourceMetadata, planOptions);
94
+ }
95
+ async apply(plan) {
96
+ if (plan.getResourceType() !== this.typeId) {
97
+ throw new Error(`Internal error: Plan set to wrong resource during apply. Expected ${this.typeId} but got: ${plan.getResourceType()}`);
98
+ }
99
+ switch (plan.changeSet.operation) {
100
+ case ResourceOperation.CREATE: {
101
+ return this._applyCreate(plan);
102
+ }
103
+ case ResourceOperation.MODIFY: {
104
+ return this._applyModify(plan);
105
+ }
106
+ case ResourceOperation.RECREATE: {
107
+ await this._applyDestroy(plan);
108
+ return this._applyCreate(plan);
109
+ }
110
+ case ResourceOperation.DESTROY: {
111
+ return this._applyDestroy(plan);
112
+ }
113
+ }
114
+ }
115
+ async _applyCreate(plan) {
116
+ await this.applyCreate(plan);
117
+ const statefulParameterChanges = plan.changeSet.parameterChanges
118
+ .filter((pc) => this.statefulParameters.has(pc.name))
119
+ .sort((a, b) => this.statefulParameterOrder.get(a.name) - this.statefulParameterOrder.get(b.name));
120
+ for (const parameterChange of statefulParameterChanges) {
121
+ const statefulParameter = this.statefulParameters.get(parameterChange.name);
122
+ await statefulParameter.applyAdd(parameterChange.newValue, plan);
123
+ }
124
+ }
125
+ async _applyModify(plan) {
126
+ const parameterChanges = plan
127
+ .changeSet
128
+ .parameterChanges
129
+ .filter((c) => c.operation !== ParameterOperation.NOOP);
130
+ const statelessParameterChanges = parameterChanges
131
+ .filter((pc) => !this.statefulParameters.has(pc.name));
132
+ for (const pc of statelessParameterChanges) {
133
+ await this.applyModify(pc, plan);
134
+ }
135
+ const statefulParameterChanges = parameterChanges
136
+ .filter((pc) => this.statefulParameters.has(pc.name))
137
+ .sort((a, b) => this.statefulParameterOrder.get(a.name) - this.statefulParameterOrder.get(b.name));
138
+ for (const parameterChange of statefulParameterChanges) {
139
+ const statefulParameter = this.statefulParameters.get(parameterChange.name);
140
+ switch (parameterChange.operation) {
141
+ case ParameterOperation.ADD: {
142
+ await statefulParameter.applyAdd(parameterChange.newValue, plan);
143
+ break;
144
+ }
145
+ case ParameterOperation.MODIFY: {
146
+ await statefulParameter.applyModify(parameterChange.newValue, parameterChange.previousValue, false, plan);
147
+ break;
148
+ }
149
+ case ParameterOperation.REMOVE: {
150
+ await statefulParameter.applyRemove(parameterChange.previousValue, plan);
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ async _applyDestroy(plan) {
157
+ if (this.options.callStatefulParameterRemoveOnDestroy) {
158
+ const statefulParameterChanges = plan.changeSet.parameterChanges
159
+ .filter((pc) => this.statefulParameters.has(pc.name))
160
+ .sort((a, b) => this.statefulParameterOrder.get(a.name) - this.statefulParameterOrder.get(b.name));
161
+ for (const parameterChange of statefulParameterChanges) {
162
+ const statefulParameter = this.statefulParameters.get(parameterChange.name);
163
+ await statefulParameter.applyRemove(parameterChange.previousValue, plan);
164
+ }
165
+ }
166
+ await this.applyDestroy(plan);
167
+ }
168
+ validateRefreshResults(refresh, desired) {
169
+ if (!refresh) {
170
+ return;
171
+ }
172
+ const desiredKeys = new Set(Object.keys(refresh));
173
+ const refreshKeys = new Set(Object.keys(refresh));
174
+ if (!setsEqual(desiredKeys, refreshKeys)) {
175
+ throw new Error(`Resource ${this.typeId}
176
+ refresh() must return back exactly the keys that were provided
177
+ Missing: ${[...desiredKeys].filter((k) => !refreshKeys.has(k))};
178
+ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
179
+ }
180
+ }
181
+ async applyTransformParameters(desired) {
182
+ if (!desired) {
183
+ return;
184
+ }
185
+ const transformParameters = [...this.transformParameters.entries()]
186
+ .sort(([keyA], [keyB]) => this.transformParameterOrder.get(keyA) - this.transformParameterOrder.get(keyB));
187
+ for (const [key, transformParameter] of transformParameters) {
188
+ if (desired[key] === undefined) {
189
+ continue;
190
+ }
191
+ const transformedValue = await transformParameter.transform(desired[key]);
192
+ if (Object.keys(transformedValue).some((k) => desired[k] !== undefined)) {
193
+ throw new Error(`Transform parameter ${key} is attempting to override existing values ${JSON.stringify(transformedValue, null, 2)}`);
194
+ }
195
+ delete desired[key];
196
+ for (const [tvKey, tvValue] of Object.entries(transformedValue)) {
197
+ desired[tvKey] = tvValue;
198
+ }
199
+ }
200
+ }
201
+ addDefaultValues(desired) {
202
+ if (!desired) {
203
+ return;
204
+ }
205
+ for (const [key, defaultValue] of Object.entries(this.defaultValues)) {
206
+ if (defaultValue !== undefined && desired[key] === undefined) {
207
+ desired[key] = defaultValue;
208
+ }
209
+ }
210
+ }
211
+ async refreshNonStatefulParameters(resourceParameters) {
212
+ const currentParameters = await this.refresh(resourceParameters);
213
+ this.validateRefreshResults(currentParameters, resourceParameters);
214
+ return currentParameters;
215
+ }
216
+ async refreshStatefulParameters(statefulParametersConfig, isStatefulMode) {
217
+ const currentParameters = {};
218
+ const sortedEntries = Object.entries(statefulParametersConfig)
219
+ .sort(([key1], [key2]) => this.statefulParameterOrder.get(key1) - this.statefulParameterOrder.get(key2));
220
+ for (const [key, desiredValue] of sortedEntries) {
221
+ const statefulParameter = this.statefulParameters.get(key);
222
+ if (!statefulParameter) {
223
+ throw new Error(`Stateful parameter ${key} was not found`);
224
+ }
225
+ let currentValue = await statefulParameter.refresh(desiredValue ?? null);
226
+ if (Array.isArray(currentValue)
227
+ && Array.isArray(desiredValue)
228
+ && !isStatefulMode
229
+ && !statefulParameter.options.disableStatelessModeArrayFiltering) {
230
+ currentValue = currentValue.filter((c) => desiredValue?.some((d) => {
231
+ const parameterOptions = statefulParameter.options;
232
+ if (parameterOptions && parameterOptions.isElementEqual) {
233
+ return parameterOptions.isElementEqual(d, c);
234
+ }
235
+ return d === c;
236
+ }));
237
+ }
238
+ currentParameters[key] = currentValue;
239
+ }
240
+ return currentParameters;
241
+ }
242
+ validatePlanInputs(desired, current, statefulMode) {
243
+ if (!desired && !current) {
244
+ throw new Error('Desired config and current config cannot both be missing');
245
+ }
246
+ if (!statefulMode && !desired) {
247
+ throw new Error('Desired config must be provided in non-stateful mode');
248
+ }
249
+ }
250
+ async customValidation(parameters) { }
251
+ ;
252
+ async applyModify(pc, plan) { }
253
+ ;
254
+ }
255
+ class ConfigParser {
256
+ desiredConfig;
257
+ currentConfig;
258
+ statefulParametersMap;
259
+ transformParametersMap;
260
+ constructor(desiredConfig, currentConfig, statefulParameters, transformParameters) {
261
+ this.desiredConfig = desiredConfig;
262
+ this.currentConfig = currentConfig;
263
+ this.statefulParametersMap = statefulParameters;
264
+ this.transformParametersMap = transformParameters;
265
+ }
266
+ get resourceMetadata() {
267
+ const desiredMetadata = this.desiredConfig ? splitUserConfig(this.desiredConfig).resourceMetadata : undefined;
268
+ const currentMetadata = this.currentConfig ? splitUserConfig(this.currentConfig).resourceMetadata : undefined;
269
+ if (!desiredMetadata && !currentMetadata) {
270
+ throw new Error(`Unable to parse resource metadata from ${this.desiredConfig}, ${this.currentConfig}`);
271
+ }
272
+ if (currentMetadata && desiredMetadata && (Object.keys(desiredMetadata).length !== Object.keys(currentMetadata).length
273
+ || Object.entries(desiredMetadata).some(([key, value]) => currentMetadata[key] !== value))) {
274
+ throw new Error(`The metadata for the current config does not match the desired config.
275
+ Desired metadata:
276
+ ${JSON.stringify(desiredMetadata, null, 2)}
277
+
278
+ Current metadata:
279
+ ${JSON.stringify(currentMetadata, null, 2)}`);
280
+ }
281
+ return desiredMetadata ?? currentMetadata;
282
+ }
283
+ get desiredParameters() {
284
+ if (!this.desiredConfig) {
285
+ return null;
286
+ }
287
+ const { parameters } = splitUserConfig(this.desiredConfig);
288
+ return parameters;
289
+ }
290
+ get parameters() {
291
+ const desiredParameters = this.desiredConfig ? splitUserConfig(this.desiredConfig).parameters : undefined;
292
+ const currentParameters = this.currentConfig ? splitUserConfig(this.currentConfig).parameters : undefined;
293
+ return { ...desiredParameters, ...currentParameters };
294
+ }
295
+ get nonStatefulParameters() {
296
+ const { parameters } = this;
297
+ return Object.fromEntries(Object.entries(parameters).filter(([key]) => !(this.statefulParametersMap.has(key) || this.transformParametersMap.has(key))));
298
+ }
299
+ get statefulParameters() {
300
+ const { parameters } = this;
301
+ return Object.fromEntries(Object.entries(parameters).filter(([key]) => this.statefulParametersMap.has(key)));
302
+ }
303
+ }
@@ -0,0 +1,29 @@
1
+ import { Plan } from './plan.js';
2
+ import { StringIndexedObject } from 'codify-schemas';
3
+ export interface StatefulParameterOptions<V> {
4
+ isEqual?: (desired: any, current: any) => boolean;
5
+ disableStatelessModeArrayFiltering?: boolean;
6
+ default?: V;
7
+ }
8
+ export interface ArrayStatefulParameterOptions<V> extends StatefulParameterOptions<V> {
9
+ isEqual?: (desired: any[], current: any[]) => boolean;
10
+ isElementEqual?: (desired: any, current: any) => boolean;
11
+ }
12
+ export declare abstract class StatefulParameter<T extends StringIndexedObject, V extends T[keyof T]> {
13
+ readonly options: StatefulParameterOptions<V>;
14
+ constructor(options?: StatefulParameterOptions<V>);
15
+ abstract refresh(desired: V | null): Promise<V | null>;
16
+ abstract applyAdd(valueToAdd: V, plan: Plan<T>): Promise<void>;
17
+ abstract applyModify(newValue: V, previousValue: V, allowDeletes: boolean, plan: Plan<T>): Promise<void>;
18
+ abstract applyRemove(valueToRemove: V, plan: Plan<T>): Promise<void>;
19
+ }
20
+ export declare abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> extends StatefulParameter<T, any> {
21
+ options: ArrayStatefulParameterOptions<V>;
22
+ constructor(options?: ArrayStatefulParameterOptions<V>);
23
+ applyAdd(valuesToAdd: V[], plan: Plan<T>): Promise<void>;
24
+ applyModify(newValues: V[], previousValues: V[], allowDeletes: boolean, plan: Plan<T>): Promise<void>;
25
+ applyRemove(valuesToRemove: V[], plan: Plan<T>): Promise<void>;
26
+ abstract refresh(desired: V[] | null): Promise<V[] | null>;
27
+ abstract applyAddItem(item: V, plan: Plan<T>): Promise<void>;
28
+ abstract applyRemoveItem(item: V, plan: Plan<T>): Promise<void>;
29
+ }
@@ -0,0 +1,46 @@
1
+ export class StatefulParameter {
2
+ options;
3
+ constructor(options = {}) {
4
+ this.options = options;
5
+ }
6
+ }
7
+ export class ArrayStatefulParameter extends StatefulParameter {
8
+ options;
9
+ constructor(options = {}) {
10
+ super(options);
11
+ this.options = options;
12
+ }
13
+ async applyAdd(valuesToAdd, plan) {
14
+ for (const value of valuesToAdd) {
15
+ await this.applyAddItem(value, plan);
16
+ }
17
+ }
18
+ async applyModify(newValues, previousValues, allowDeletes, plan) {
19
+ const options = this.options;
20
+ const valuesToAdd = newValues.filter((n) => !previousValues.some((p) => {
21
+ if (options.isElementEqual) {
22
+ return options.isElementEqual(n, p);
23
+ }
24
+ return n === p;
25
+ }));
26
+ const valuesToRemove = previousValues.filter((p) => !newValues.some((n) => {
27
+ if (options.isElementEqual) {
28
+ return options.isElementEqual(n, p);
29
+ }
30
+ return n === p;
31
+ }));
32
+ for (const value of valuesToAdd) {
33
+ await this.applyAddItem(value, plan);
34
+ }
35
+ if (allowDeletes) {
36
+ for (const value of valuesToRemove) {
37
+ await this.applyRemoveItem(value, plan);
38
+ }
39
+ }
40
+ }
41
+ async applyRemove(valuesToRemove, plan) {
42
+ for (const value of valuesToRemove) {
43
+ await this.applyRemoveItem(value, plan);
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,4 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ export declare abstract class TransformParameter<T extends StringIndexedObject> {
3
+ abstract transform(value: any): Promise<Partial<T>>;
4
+ }
@@ -0,0 +1,2 @@
1
+ export class TransformParameter {
2
+ }
@@ -0,0 +1,4 @@
1
+ export declare class SudoError extends Error {
2
+ command: string;
3
+ constructor(command: string);
4
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,7 @@
1
+ export class SudoError extends Error {
2
+ command;
3
+ constructor(command) {
4
+ super();
5
+ this.command = command;
6
+ }
7
+ }
@@ -0,0 +1,20 @@
1
+ import { Plugin } from './plugin/plugin.js';
2
+ export * from './errors.js';
3
+ export * from './messages/sender.js';
4
+ export * from './plan/change-set.js';
5
+ export * from './plan/plan.js';
6
+ export * from './plan/plan-types.js';
7
+ export * from './plugin/plugin.js';
8
+ export * from './pty/background-pty.js';
9
+ export * from './pty/index.js';
10
+ export * from './pty/seqeuntial-pty.js';
11
+ export * from './resource/parsed-resource-settings.js';
12
+ export * from './resource/resource.js';
13
+ export * from './resource/resource-settings.js';
14
+ export * from './stateful-parameter/stateful-parameter.js';
15
+ export * from './utils/file-utils.js';
16
+ export * from './utils/functions.js';
17
+ export * from './utils/index.js';
18
+ export * from './utils/verbosity-level.js';
19
+ export * from 'zod/v4';
20
+ export declare function runPlugin(plugin: Plugin): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ import { MessageHandler } from './messages/handlers.js';
2
+ export * from './errors.js';
3
+ export * from './messages/sender.js';
4
+ export * from './plan/change-set.js';
5
+ export * from './plan/plan.js';
6
+ export * from './plan/plan-types.js';
7
+ export * from './plugin/plugin.js';
8
+ export * from './pty/background-pty.js';
9
+ export * from './pty/index.js';
10
+ export * from './pty/seqeuntial-pty.js';
11
+ export * from './resource/parsed-resource-settings.js';
12
+ export * from './resource/resource.js';
13
+ export * from './resource/resource-settings.js';
14
+ export * from './stateful-parameter/stateful-parameter.js';
15
+ export * from './utils/file-utils.js';
16
+ export * from './utils/functions.js';
17
+ export * from './utils/index.js';
18
+ export * from './utils/verbosity-level.js';
19
+ export * from 'zod/v4';
20
+ export async function runPlugin(plugin) {
21
+ const messageHandler = new MessageHandler(plugin);
22
+ process.on('message', (message) => messageHandler.onMessage(message));
23
+ process.on('beforeExit', () => {
24
+ plugin.kill();
25
+ });
26
+ }
@@ -0,0 +1,14 @@
1
+ import { Plugin } from '../plugin/plugin.js';
2
+ export declare class MessageHandler {
3
+ private ajv;
4
+ private readonly plugin;
5
+ private messageSchemaValidatorV1;
6
+ private messageSchemaValidatorV2;
7
+ private requestValidators;
8
+ private responseValidators;
9
+ constructor(plugin: Plugin);
10
+ onMessage(message: unknown): Promise<void>;
11
+ private validateMessage;
12
+ private validateMessageV2;
13
+ private handleErrors;
14
+ }
@@ -0,0 +1,134 @@
1
+ import { Ajv } from 'ajv';
2
+ import addFormats from 'ajv-formats';
3
+ import { ApplyRequestDataSchema, EmptyResponseDataSchema, GetResourceInfoRequestDataSchema, GetResourceInfoResponseDataSchema, ImportRequestDataSchema, ImportResponseDataSchema, InitializeRequestDataSchema, InitializeResponseDataSchema, IpcMessageSchema, IpcMessageV2Schema, MatchRequestDataSchema, MatchResponseDataSchema, MessageStatus, PlanRequestDataSchema, PlanResponseDataSchema, ResourceSchema, SetVerbosityRequestDataSchema, ValidateRequestDataSchema, ValidateResponseDataSchema } from 'codify-schemas';
4
+ import { SudoError } from '../errors.js';
5
+ const SupportedRequests = {
6
+ 'initialize': {
7
+ handler: async (plugin, data) => plugin.initialize(data),
8
+ requestValidator: InitializeRequestDataSchema,
9
+ responseValidator: InitializeResponseDataSchema
10
+ },
11
+ 'validate': {
12
+ handler: async (plugin, data) => plugin.validate(data),
13
+ requestValidator: ValidateRequestDataSchema,
14
+ responseValidator: ValidateResponseDataSchema
15
+ },
16
+ 'getResourceInfo': {
17
+ handler: async (plugin, data) => plugin.getResourceInfo(data),
18
+ requestValidator: GetResourceInfoRequestDataSchema,
19
+ responseValidator: GetResourceInfoResponseDataSchema
20
+ },
21
+ 'setVerbosityLevel': {
22
+ async handler(plugin, data) {
23
+ await plugin.setVerbosityLevel(data);
24
+ return null;
25
+ },
26
+ requestValidator: SetVerbosityRequestDataSchema,
27
+ responseValidator: EmptyResponseDataSchema,
28
+ },
29
+ 'match': {
30
+ handler: async (plugin, data) => plugin.match(data),
31
+ requestValidator: MatchRequestDataSchema,
32
+ responseValidator: MatchResponseDataSchema
33
+ },
34
+ 'import': {
35
+ handler: async (plugin, data) => plugin.import(data),
36
+ requestValidator: ImportRequestDataSchema,
37
+ responseValidator: ImportResponseDataSchema
38
+ },
39
+ 'plan': {
40
+ handler: async (plugin, data) => plugin.plan(data),
41
+ requestValidator: PlanRequestDataSchema,
42
+ responseValidator: PlanResponseDataSchema
43
+ },
44
+ 'apply': {
45
+ async handler(plugin, data) {
46
+ await plugin.apply(data);
47
+ return null;
48
+ },
49
+ requestValidator: ApplyRequestDataSchema,
50
+ responseValidator: EmptyResponseDataSchema
51
+ },
52
+ };
53
+ export class MessageHandler {
54
+ ajv;
55
+ plugin;
56
+ messageSchemaValidatorV1;
57
+ messageSchemaValidatorV2;
58
+ requestValidators;
59
+ responseValidators;
60
+ constructor(plugin) {
61
+ this.ajv = new Ajv({ strict: true, strictRequired: false });
62
+ addFormats.default(this.ajv);
63
+ this.ajv.addSchema(ResourceSchema);
64
+ this.plugin = plugin;
65
+ this.messageSchemaValidatorV1 = this.ajv.compile(IpcMessageSchema);
66
+ this.messageSchemaValidatorV2 = this.ajv.compile(IpcMessageV2Schema);
67
+ this.requestValidators = new Map(Object.entries(SupportedRequests)
68
+ .map(([k, v]) => [k, this.ajv.compile(v.requestValidator)]));
69
+ this.responseValidators = new Map(Object.entries(SupportedRequests)
70
+ .map(([k, v]) => [k, this.ajv.compile(v.responseValidator)]));
71
+ }
72
+ async onMessage(message) {
73
+ try {
74
+ if (!this.validateMessageV2(message) && !this.validateMessage(message)) {
75
+ throw new Error(`Plugin: ${this.plugin}. Message is malformed: ${JSON.stringify(this.messageSchemaValidatorV1.errors, null, 2)}`);
76
+ }
77
+ if (!this.requestValidators.has(message.cmd)) {
78
+ throw new Error(`Plugin: ${this.plugin}. Unsupported message: ${message.cmd}`);
79
+ }
80
+ const requestValidator = this.requestValidators.get(message.cmd);
81
+ if (!requestValidator(message.data)) {
82
+ throw new Error(`Plugin: ${this.plugin}. cmd: ${message.cmd}. Malformed message data: ${JSON.stringify(requestValidator.errors, null, 2)}`);
83
+ }
84
+ const result = await SupportedRequests[message.cmd].handler(this.plugin, message.data);
85
+ const responseValidator = this.responseValidators.get(message.cmd);
86
+ if (responseValidator && !responseValidator(result)) {
87
+ throw new Error(`Plugin: ${this.plugin.name}. Malformed response data: ${JSON.stringify(responseValidator.errors, null, 2)}. Received ${JSON.stringify(result, null, 2)}`);
88
+ }
89
+ process.send({
90
+ cmd: message.cmd + '_Response',
91
+ data: result,
92
+ // @ts-expect-error TS2239
93
+ requestId: message.requestId || undefined,
94
+ status: MessageStatus.SUCCESS,
95
+ });
96
+ }
97
+ catch (error) {
98
+ this.handleErrors(message, error);
99
+ }
100
+ }
101
+ validateMessage(message) {
102
+ return this.messageSchemaValidatorV1(message);
103
+ }
104
+ validateMessageV2(message) {
105
+ return this.messageSchemaValidatorV2(message);
106
+ }
107
+ handleErrors(message, e) {
108
+ if (!message) {
109
+ return;
110
+ }
111
+ if (!message.hasOwnProperty('cmd')) {
112
+ return;
113
+ }
114
+ // @ts-expect-error TS2239
115
+ const cmd = message.cmd + '_Response';
116
+ if (e instanceof SudoError) {
117
+ return process.send?.({
118
+ cmd,
119
+ // @ts-expect-error TS2239
120
+ requestId: message.requestId || undefined,
121
+ data: `Plugin: '${this.plugin.name}'. Forbidden usage of sudo for command '${e.command}'. Please contact the plugin developer to fix this.`,
122
+ status: MessageStatus.ERROR,
123
+ });
124
+ }
125
+ const isDebug = process.env.DEBUG?.includes('*') ?? false;
126
+ process.send?.({
127
+ cmd,
128
+ // @ts-expect-error TS2239
129
+ requestId: message.requestId || undefined,
130
+ data: isDebug ? e.stack : e.message,
131
+ status: MessageStatus.ERROR,
132
+ });
133
+ }
134
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Send requests to the Codify CLI
3
+ */
4
+ declare class CodifyCliSenderImpl {
5
+ private readonly validateIpcMessageV2;
6
+ requestPressKeyToContinuePrompt(message?: string): Promise<void>;
7
+ getCodifyCliCredentials(): Promise<string>;
8
+ private sendAndWaitForResponse;
9
+ }
10
+ export declare const CodifyCliSender: CodifyCliSenderImpl;
11
+ export {};
@@ -0,0 +1,57 @@
1
+ import { Ajv } from 'ajv';
2
+ import { IpcMessageV2Schema, MessageCmd } from 'codify-schemas';
3
+ import { nanoid } from 'nanoid';
4
+ const ajv = new Ajv({
5
+ strict: true,
6
+ });
7
+ /**
8
+ * Send requests to the Codify CLI
9
+ */
10
+ class CodifyCliSenderImpl {
11
+ validateIpcMessageV2 = ajv.compile(IpcMessageV2Schema);
12
+ async requestPressKeyToContinuePrompt(message) {
13
+ await this.sendAndWaitForResponse({
14
+ cmd: MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST,
15
+ data: {
16
+ promptMessage: message,
17
+ }
18
+ });
19
+ }
20
+ async getCodifyCliCredentials() {
21
+ const data = await this.sendAndWaitForResponse({
22
+ cmd: MessageCmd.CODIFY_CREDENTIALS_REQUEST,
23
+ data: {},
24
+ });
25
+ if (typeof data.data !== 'string') {
26
+ throw new Error('Expected string back from credentials request');
27
+ }
28
+ return data.data;
29
+ }
30
+ async sendAndWaitForResponse(message) {
31
+ return new Promise((resolve) => {
32
+ const requestId = nanoid(8);
33
+ const listener = (data) => {
34
+ if (data.requestId === requestId) {
35
+ process.removeListener('message', listener);
36
+ if (!this.validateIpcMessageV2(data)) {
37
+ throw new Error(`Invalid response for request.
38
+ Request:
39
+ ${JSON.stringify(message, null, 2)}
40
+ Response:
41
+ ${JSON.stringify(data, null, 2)}
42
+ Error:
43
+ ${JSON.stringify(this.validateIpcMessageV2.errors, null, 2)}`);
44
+ }
45
+ resolve(data);
46
+ }
47
+ };
48
+ process.on('message', listener);
49
+ const ipcMessage = {
50
+ ...message,
51
+ requestId,
52
+ };
53
+ process.send(ipcMessage);
54
+ });
55
+ }
56
+ }
57
+ export const CodifyCliSender = new CodifyCliSenderImpl();