codify-plugin-lib 1.0.75 → 1.0.77

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 (76) hide show
  1. package/.eslintrc.json +11 -4
  2. package/.github/workflows/release.yaml +19 -0
  3. package/.github/workflows/unit-test-ci.yaml +19 -0
  4. package/dist/entities/plugin.d.ts +1 -1
  5. package/dist/entities/plugin.js +5 -5
  6. package/dist/entities/resource-options.d.ts +6 -6
  7. package/dist/entities/resource-options.js +7 -9
  8. package/dist/entities/resource.d.ts +2 -3
  9. package/dist/entities/resource.js +2 -2
  10. package/dist/errors.d.ts +4 -0
  11. package/dist/errors.js +7 -0
  12. package/dist/index.d.ts +10 -10
  13. package/dist/index.js +9 -9
  14. package/dist/messages/handlers.d.ts +1 -1
  15. package/dist/messages/handlers.js +25 -24
  16. package/dist/plan/change-set.d.ts +37 -0
  17. package/dist/plan/change-set.js +146 -0
  18. package/dist/plan/plan-types.d.ts +23 -0
  19. package/dist/plan/plan-types.js +1 -0
  20. package/dist/plan/plan.d.ts +59 -0
  21. package/dist/plan/plan.js +228 -0
  22. package/dist/plugin/plugin.d.ts +17 -0
  23. package/dist/plugin/plugin.js +83 -0
  24. package/dist/resource/config-parser.d.ts +14 -0
  25. package/dist/resource/config-parser.js +48 -0
  26. package/dist/resource/parsed-resource-settings.d.ts +26 -0
  27. package/dist/resource/parsed-resource-settings.js +126 -0
  28. package/dist/resource/resource-controller.d.ts +30 -0
  29. package/dist/resource/resource-controller.js +247 -0
  30. package/dist/resource/resource-settings.d.ts +149 -0
  31. package/dist/resource/resource-settings.js +9 -0
  32. package/dist/resource/resource.d.ts +137 -0
  33. package/dist/resource/resource.js +44 -0
  34. package/dist/resource/stateful-parameter.d.ts +164 -0
  35. package/dist/resource/stateful-parameter.js +94 -0
  36. package/dist/utils/utils.d.ts +19 -3
  37. package/dist/utils/utils.js +52 -3
  38. package/package.json +6 -4
  39. package/src/index.ts +10 -11
  40. package/src/messages/handlers.test.ts +21 -42
  41. package/src/messages/handlers.ts +28 -27
  42. package/src/plan/change-set.test.ts +220 -0
  43. package/src/plan/change-set.ts +225 -0
  44. package/src/plan/plan-types.ts +27 -0
  45. package/src/{entities → plan}/plan.test.ts +35 -29
  46. package/src/plan/plan.ts +353 -0
  47. package/src/{entities → plugin}/plugin.test.ts +14 -13
  48. package/src/{entities → plugin}/plugin.ts +32 -27
  49. package/src/resource/config-parser.ts +77 -0
  50. package/src/{entities/resource-options.test.ts → resource/parsed-resource-settings.test.ts} +8 -7
  51. package/src/resource/parsed-resource-settings.ts +179 -0
  52. package/src/{entities/resource-stateful-mode.test.ts → resource/resource-controller-stateful-mode.test.ts} +36 -39
  53. package/src/{entities/resource.test.ts → resource/resource-controller.test.ts} +116 -176
  54. package/src/resource/resource-controller.ts +340 -0
  55. package/src/resource/resource-settings.test.ts +494 -0
  56. package/src/resource/resource-settings.ts +192 -0
  57. package/src/resource/resource.ts +149 -0
  58. package/src/resource/stateful-parameter.test.ts +93 -0
  59. package/src/resource/stateful-parameter.ts +217 -0
  60. package/src/utils/test-utils.test.ts +87 -0
  61. package/src/utils/utils.test.ts +2 -2
  62. package/src/utils/utils.ts +51 -5
  63. package/tsconfig.json +0 -1
  64. package/vitest.config.ts +10 -0
  65. package/src/entities/change-set.test.ts +0 -155
  66. package/src/entities/change-set.ts +0 -244
  67. package/src/entities/plan-types.ts +0 -44
  68. package/src/entities/plan.ts +0 -178
  69. package/src/entities/resource-options.ts +0 -156
  70. package/src/entities/resource-parameters.test.ts +0 -604
  71. package/src/entities/resource-types.ts +0 -31
  72. package/src/entities/resource.ts +0 -471
  73. package/src/entities/stateful-parameter.test.ts +0 -114
  74. package/src/entities/stateful-parameter.ts +0 -92
  75. package/src/entities/transform-parameter.ts +0 -13
  76. /package/src/{entities/errors.ts → errors.ts} +0 -0
@@ -0,0 +1,164 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ import { Plan } from '../plan/plan.js';
3
+ import { ArrayParameterSetting, ParameterSetting } from './resource-settings.js';
4
+ /**
5
+ * A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
6
+ * is still tied to the overall lifecycle of a resource.
7
+ *
8
+ * **Examples include:**
9
+ * 1. Homebrew formulas are stateful parameters. They can be installed and uninstalled but they are still tied to the
10
+ * overall lifecycle of homebrew
11
+ * 2. Nvm installed node versions are stateful parameters. Nvm can install and uninstall different versions of Node but
12
+ * these versions are tied to the lifecycle of nvm. If nvm is uninstalled then so are the Node versions.
13
+ */
14
+ export declare abstract class StatefulParameter<T extends StringIndexedObject, V extends T[keyof T]> {
15
+ /**
16
+ * Parameter settings for the stateful parameter. Stateful parameters share the same parameter settings as
17
+ * regular parameters except that they cannot be of type 'stateful'. See {@link ParameterSetting} for more
18
+ * information on available settings.
19
+ *
20
+ * @return The parameter settings
21
+ */
22
+ getSettings(): ParameterSetting;
23
+ /**
24
+ * Refresh the status of the stateful parameter on the system. This method works similarly to {@link Resource.refresh}.
25
+ * Return the value of the stateful parameter or null if not found.
26
+ *
27
+ * @param desired The desired value of the user.
28
+ *
29
+ * @return The value of the stateful parameter currently on the system or null if not found
30
+ */
31
+ abstract refresh(desired: V | null): Promise<V | null>;
32
+ /**
33
+ * Create the stateful parameter on the system. This method is similar {@link Resource.create} except that its only
34
+ * applicable to the stateful parameter. For resource `CREATE` operations, this method will be called after the
35
+ * resource is successfully created. The add method is called when a ParameterChange is ADD in a plan. The add
36
+ * method is only called when the stateful parameter does not currently exist.
37
+ *
38
+ * **Example (Homebrew formula):**
39
+ * 1. Add is called with a value of:
40
+ * ```
41
+ * ['jq', 'jenv']
42
+ * ```
43
+ * 2. Add handles the request by calling `brew install --formulae jq jenv`
44
+ *
45
+ * @param valueToAdd The desired value of the stateful parameter.
46
+ * @param plan The overall plan that contains the ADD
47
+ */
48
+ abstract add(valueToAdd: V, plan: Plan<T>): Promise<void>;
49
+ /**
50
+ * Modify the state of a stateful parameter on the system. This method is similar to {@link Resource.modify} except that its only
51
+ * applicable to the stateful parameter.
52
+ *
53
+ * **Example (Git email parameter):**
54
+ * 1. Add is called with a value of:
55
+ * ```
56
+ * newValue: 'email+new@gmail.com', previousValue: 'email+old@gmail.com'
57
+ * ```
58
+ * 2. Modify handles the request by calling `git config --global user.email email+new@gmail.com`
59
+ *
60
+ * @param newValue The desired value of the stateful parameter
61
+ * @param previousValue The current value of the stateful parameter
62
+ * @param plan The overall plan
63
+ */
64
+ abstract modify(newValue: V, previousValue: V, plan: Plan<T>): Promise<void>;
65
+ /**
66
+ * Create the stateful parameter on the system. This method is similar {@link Resource.destroy} except that its only
67
+ * applicable to the stateful parameter. The remove method is only called when the stateful parameter already currently exist.
68
+ * This method corresponds to REMOVE parameter operations in a plan.
69
+ * For resource `DESTORY`, this method is only called if the {@link ResourceSettings.removeStatefulParametersBeforeDestroy}
70
+ * is set to true. This method will be called before the resource is destroyed.
71
+ *
72
+ * **Example (Homebrew formula):**
73
+ * 1. Remove is called with a value of:
74
+ * ```
75
+ * ['jq', 'jenv']
76
+ * ```
77
+ * 2. Remove handles the request by calling `brew uninstall --formulae jq jenv`
78
+ *
79
+ * @param valueToRemove The value to remove from the stateful parameter.
80
+ * @param plan The overall plan that contains the REMOVE
81
+ */
82
+ abstract remove(valueToRemove: V, plan: Plan<T>): Promise<void>;
83
+ }
84
+ /**
85
+ * A specialized version of {@link StatefulParameter } that is used for stateful parameters which are arrays.
86
+ * A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
87
+ * is still tied to the overall lifecycle of a resource.
88
+ *
89
+ * **Examples:**
90
+ * - Homebrew formulas are arrays
91
+ * - Pyenv python versions are arrays
92
+ */
93
+ export declare abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> extends StatefulParameter<T, any> {
94
+ /**
95
+ * Parameter level settings. Type must be 'array'.
96
+ */
97
+ getSettings(): ArrayParameterSetting;
98
+ /**
99
+ * It is not recommended to override the `add` method. A addItem helper method is available to operate on
100
+ * individual elements of the desired array. See {@link StatefulParameter.add} for more info.
101
+ *
102
+ * @param valuesToAdd The array of values to add
103
+ * @param plan The overall plan
104
+ *
105
+ */
106
+ add(valuesToAdd: V[], plan: Plan<T>): Promise<void>;
107
+ /**
108
+ * It is not recommended to override the `modify` method. `addItem` and `removeItem` will be called accordingly based
109
+ * on the modifications. See {@link StatefulParameter.modify} for more info.
110
+ *
111
+ * @param newValues The new array value
112
+ * @param previousValues The previous array value
113
+ * @param plan The overall plan
114
+ */
115
+ modify(newValues: V[], previousValues: V[], plan: Plan<T>): Promise<void>;
116
+ /**
117
+ * It is not recommended to override the `remove` method. A removeItem helper method is available to operate on
118
+ * individual elements of the desired array. See {@link StatefulParameter.remove} for more info.
119
+ *
120
+ * @param valuesToAdd The array of values to add
121
+ * @param plan The overall plan
122
+ *
123
+ */
124
+ remove(valuesToRemove: V[], plan: Plan<T>): Promise<void>;
125
+ /**
126
+ * See {@link StatefulParameter.refresh} for more info.
127
+ *
128
+ * @param desired The desired value to refresh
129
+ * @return The current value on the system or null if not found.
130
+ */
131
+ abstract refresh(desired: V[] | null): Promise<V[] | null>;
132
+ /**
133
+ * Helper method that gets called when individual elements of the array need to be added. See {@link StatefulParameter.add}
134
+ * for more information.
135
+ *
136
+ * Example (Homebrew formula):
137
+ * 1. The stateful parameter receives an input of:
138
+ * ```
139
+ * ['jq', 'jenv', 'docker']
140
+ * ```
141
+ * 2. Internally the stateful parameter will iterate the array and call `addItem` for each element
142
+ * 3. Override addItem and install each formula using `brew install --formula jq`
143
+ *
144
+ * @param item The item to add (install)
145
+ * @param plan The overall plan
146
+ */
147
+ abstract addItem(item: V, plan: Plan<T>): Promise<void>;
148
+ /**
149
+ * Helper method that gets called when individual elements of the array need to be removed. See {@link StatefulParameter.remove}
150
+ * for more information.
151
+ *
152
+ * Example (Homebrew formula):
153
+ * 1. The stateful parameter receives an input of:
154
+ * ```
155
+ * ['jq', 'jenv', 'docker']
156
+ * ```
157
+ * 2. Internally the stateful parameter will iterate the array and call `removeItem` for each element
158
+ * 3. Override removeItem and uninstall each formula using `brew uninstall --formula jq`
159
+ *
160
+ * @param item The item to remove (uninstall)
161
+ * @param plan The overall plan
162
+ */
163
+ abstract removeItem(item: V, plan: Plan<T>): Promise<void>;
164
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
3
+ * is still tied to the overall lifecycle of a resource.
4
+ *
5
+ * **Examples include:**
6
+ * 1. Homebrew formulas are stateful parameters. They can be installed and uninstalled but they are still tied to the
7
+ * overall lifecycle of homebrew
8
+ * 2. Nvm installed node versions are stateful parameters. Nvm can install and uninstall different versions of Node but
9
+ * these versions are tied to the lifecycle of nvm. If nvm is uninstalled then so are the Node versions.
10
+ */
11
+ export class StatefulParameter {
12
+ /**
13
+ * Parameter settings for the stateful parameter. Stateful parameters share the same parameter settings as
14
+ * regular parameters except that they cannot be of type 'stateful'. See {@link ParameterSetting} for more
15
+ * information on available settings.
16
+ *
17
+ * @return The parameter settings
18
+ */
19
+ getSettings() {
20
+ return {};
21
+ }
22
+ }
23
+ /**
24
+ * A specialized version of {@link StatefulParameter } that is used for stateful parameters which are arrays.
25
+ * A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
26
+ * is still tied to the overall lifecycle of a resource.
27
+ *
28
+ * **Examples:**
29
+ * - Homebrew formulas are arrays
30
+ * - Pyenv python versions are arrays
31
+ */
32
+ export class ArrayStatefulParameter extends StatefulParameter {
33
+ /**
34
+ * Parameter level settings. Type must be 'array'.
35
+ */
36
+ getSettings() {
37
+ return { type: 'array' };
38
+ }
39
+ /**
40
+ * It is not recommended to override the `add` method. A addItem helper method is available to operate on
41
+ * individual elements of the desired array. See {@link StatefulParameter.add} for more info.
42
+ *
43
+ * @param valuesToAdd The array of values to add
44
+ * @param plan The overall plan
45
+ *
46
+ */
47
+ async add(valuesToAdd, plan) {
48
+ for (const value of valuesToAdd) {
49
+ await this.addItem(value, plan);
50
+ }
51
+ }
52
+ /**
53
+ * It is not recommended to override the `modify` method. `addItem` and `removeItem` will be called accordingly based
54
+ * on the modifications. See {@link StatefulParameter.modify} for more info.
55
+ *
56
+ * @param newValues The new array value
57
+ * @param previousValues The previous array value
58
+ * @param plan The overall plan
59
+ */
60
+ async modify(newValues, previousValues, plan) {
61
+ // TODO: I don't think this works with duplicate elements. Solve at another time
62
+ const valuesToAdd = newValues.filter((n) => !previousValues.some((p) => {
63
+ if (this.getSettings()?.isElementEqual) {
64
+ return this.getSettings().isElementEqual(n, p);
65
+ }
66
+ return n === p;
67
+ }));
68
+ const valuesToRemove = previousValues.filter((p) => !newValues.some((n) => {
69
+ if (this.getSettings().isElementEqual) {
70
+ return this.getSettings().isElementEqual(n, p);
71
+ }
72
+ return n === p;
73
+ }));
74
+ for (const value of valuesToAdd) {
75
+ await this.addItem(value, plan);
76
+ }
77
+ for (const value of valuesToRemove) {
78
+ await this.removeItem(value, plan);
79
+ }
80
+ }
81
+ /**
82
+ * It is not recommended to override the `remove` method. A removeItem helper method is available to operate on
83
+ * individual elements of the desired array. See {@link StatefulParameter.remove} for more info.
84
+ *
85
+ * @param valuesToAdd The array of values to add
86
+ * @param plan The overall plan
87
+ *
88
+ */
89
+ async remove(valuesToRemove, plan) {
90
+ for (const value of valuesToRemove) {
91
+ await this.removeItem(value, plan);
92
+ }
93
+ }
94
+ }
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import { SpawnOptions } from 'child_process';
3
2
  import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
3
+ import { SpawnOptions } from 'node:child_process';
4
+ import { ArrayParameterSetting } from '../resource/resource-settings.js';
4
5
  export declare enum SpawnStatus {
5
6
  SUCCESS = "success",
6
7
  ERROR = "error"
@@ -13,13 +14,28 @@ type CodifySpawnOptions = {
13
14
  cwd?: string;
14
15
  stdioString?: boolean;
15
16
  } & SpawnOptions;
17
+ /**
18
+ *
19
+ * @param cmd Command to run. Ex: `rm -rf`
20
+ * @param args Optional additional arguments to append
21
+ * @param opts Standard options for node spawn. Additional argument:
22
+ * throws determines if a shell will throw a JS error. Defaults to true
23
+ * @param extras From PromiseSpawn
24
+ *
25
+ * @see promiseSpawn
26
+ * @see spawn
27
+ *
28
+ * @returns SpawnResult { status: SUCCESS | ERROR; data: string }
29
+ */
16
30
  export declare function codifySpawn(cmd: string, args?: string[], opts?: Omit<CodifySpawnOptions, 'stdio' | 'stdioString'> & {
17
31
  throws?: boolean;
18
32
  }, extras?: Record<any, any>): Promise<SpawnResult>;
19
33
  export declare function isDebug(): boolean;
20
- export declare function splitUserConfig<T extends StringIndexedObject>(config: T & ResourceConfig): {
34
+ export declare function splitUserConfig<T extends StringIndexedObject>(config: ResourceConfig & T): {
21
35
  parameters: T;
22
- resourceMetadata: ResourceConfig;
36
+ coreParameters: ResourceConfig;
23
37
  };
24
38
  export declare function setsEqual(set1: Set<unknown>, set2: Set<unknown>): boolean;
39
+ export declare function untildify(pathWithTilde: string): string;
40
+ export declare function areArraysEqual(parameter: ArrayParameterSetting, desired: unknown, current: unknown): boolean;
25
41
  export {};
@@ -1,11 +1,27 @@
1
1
  import promiseSpawn from '@npmcli/promise-spawn';
2
+ import os from 'node:os';
2
3
  export var SpawnStatus;
3
4
  (function (SpawnStatus) {
4
5
  SpawnStatus["SUCCESS"] = "success";
5
6
  SpawnStatus["ERROR"] = "error";
6
7
  })(SpawnStatus || (SpawnStatus = {}));
8
+ /**
9
+ *
10
+ * @param cmd Command to run. Ex: `rm -rf`
11
+ * @param args Optional additional arguments to append
12
+ * @param opts Standard options for node spawn. Additional argument:
13
+ * throws determines if a shell will throw a JS error. Defaults to true
14
+ * @param extras From PromiseSpawn
15
+ *
16
+ * @see promiseSpawn
17
+ * @see spawn
18
+ *
19
+ * @returns SpawnResult { status: SUCCESS | ERROR; data: string }
20
+ */
7
21
  export async function codifySpawn(cmd, args, opts, extras) {
8
22
  try {
23
+ // TODO: Need to benchmark the effects of using sh vs zsh for shell.
24
+ // Seems like zsh shells run slower
9
25
  const result = await promiseSpawn(cmd, args ?? [], { ...opts, stdio: 'pipe', stdioString: true, shell: opts?.shell ?? process.env.SHELL }, extras);
10
26
  if (isDebug()) {
11
27
  console.log(`codifySpawn result for: ${cmd}`);
@@ -34,20 +50,53 @@ export async function codifySpawn(cmd, args, opts, extras) {
34
50
  }
35
51
  }
36
52
  export function isDebug() {
37
- return process.env.DEBUG != null && process.env.DEBUG.includes('codify');
53
+ return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
38
54
  }
39
55
  export function splitUserConfig(config) {
40
- const resourceMetadata = {
56
+ const coreParameters = {
41
57
  type: config.type,
42
58
  ...(config.name ? { name: config.name } : {}),
43
59
  ...(config.dependsOn ? { dependsOn: config.dependsOn } : {}),
44
60
  };
61
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
62
  const { type, name, dependsOn, ...parameters } = config;
46
63
  return {
47
64
  parameters: parameters,
48
- resourceMetadata,
65
+ coreParameters,
49
66
  };
50
67
  }
51
68
  export function setsEqual(set1, set2) {
52
69
  return set1.size === set2.size && [...set1].every((v) => set2.has(v));
53
70
  }
71
+ const homeDirectory = os.homedir();
72
+ export function untildify(pathWithTilde) {
73
+ return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
74
+ }
75
+ export function areArraysEqual(parameter, desired, current) {
76
+ if (!Array.isArray(desired) || !Array.isArray(current)) {
77
+ throw new Error(`A non-array value:
78
+
79
+ Desired: ${JSON.stringify(desired, null, 2)}
80
+
81
+ Current: ${JSON.stringify(desired, null, 2)}
82
+
83
+ Was provided even though type array was specified.
84
+ `);
85
+ }
86
+ if (desired.length !== current.length) {
87
+ return false;
88
+ }
89
+ const desiredCopy = [...desired];
90
+ const currentCopy = [...current];
91
+ // Algorithm for to check equality between two un-ordered; un-hashable arrays using
92
+ // an isElementEqual method. Time: O(n^2)
93
+ for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
94
+ const idx = currentCopy.findIndex((e2) => (parameter.isElementEqual ?? ((a, b) => a === b))(desiredCopy[counter], e2));
95
+ if (idx === -1) {
96
+ return false;
97
+ }
98
+ desiredCopy.splice(counter, 1);
99
+ currentCopy.splice(idx, 1);
100
+ }
101
+ return currentCopy.length === 0;
102
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codify-plugin-lib",
3
- "version": "1.0.75",
4
- "description": "",
3
+ "version": "1.0.77",
4
+ "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
7
7
  "type": "module",
@@ -14,8 +14,9 @@
14
14
  "dependencies": {
15
15
  "ajv": "^8.12.0",
16
16
  "ajv-formats": "^2.1.1",
17
- "codify-schemas": "1.0.44",
18
- "@npmcli/promise-spawn": "^7.0.1"
17
+ "codify-schemas": "1.0.45",
18
+ "@npmcli/promise-spawn": "^7.0.1",
19
+ "uuid": "^10.0.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@oclif/prettier-config": "^0.2.1",
@@ -24,6 +25,7 @@
24
25
  "@types/node": "^18",
25
26
  "@types/semver": "^7.5.4",
26
27
  "@types/sinon": "^17.0.3",
28
+ "@types/uuid": "^10.0.0",
27
29
  "chai-as-promised": "^7.1.1",
28
30
  "vitest": "^1.4.0",
29
31
  "vitest-mock-extended": "^1.3.1",
package/src/index.ts CHANGED
@@ -1,16 +1,15 @@
1
- import { Plugin } from './entities/plugin.js';
2
1
  import { MessageHandler } from './messages/handlers.js';
2
+ import { Plugin } from './plugin/plugin.js';
3
3
 
4
- export * from './entities/resource.js'
5
- export * from './entities/resource-types.js'
6
- export * from './entities/resource-options.js'
7
- export * from './entities/plugin.js'
8
- export * from './entities/change-set.js'
9
- export * from './entities/plan.js'
10
- export * from './entities/plan-types.js'
11
- export * from './entities/stateful-parameter.js'
12
- export * from './entities/errors.js'
13
-
4
+ export * from './errors.js'
5
+ export * from './plan/change-set.js'
6
+ export * from './plan/plan.js'
7
+ export * from './plan/plan-types.js'
8
+ export * from './plugin/plugin.js'
9
+ export * from './resource/parsed-resource-settings.js';
10
+ export * from './resource/resource.js'
11
+ export * from './resource/resource-settings.js'
12
+ export * from './resource/stateful-parameter.js'
14
13
  export * from './utils/utils.js'
15
14
 
16
15
  export async function runPlugin(plugin: Plugin) {
@@ -1,10 +1,10 @@
1
1
  import { MessageHandler } from './handlers.js';
2
- import { Plugin } from '../entities/plugin.js';
2
+ import { Plugin } from '../plugin/plugin.js';
3
3
  import { describe, expect, it } from 'vitest';
4
4
  import { mock } from 'vitest-mock-extended'
5
- import { Resource } from '../entities/resource.js';
6
- import { Plan } from '../entities/plan.js';
5
+ import { Resource } from '../resource/resource.js';
7
6
  import { MessageStatus, ResourceOperation } from 'codify-schemas';
7
+ import { TestResource } from '../utils/test-utils.test.js';
8
8
 
9
9
  describe('Message handler tests', () => {
10
10
  it('handles plan requests', async () => {
@@ -25,10 +25,13 @@ describe('Message handler tests', () => {
25
25
  await handler.onMessage({
26
26
  cmd: 'plan',
27
27
  data: {
28
- type: 'resourceType',
29
- name: 'name',
30
- prop1: 'A',
31
- prop2: 'B',
28
+ desired: {
29
+ type: 'resourceType',
30
+ name: 'name',
31
+ prop1: 'A',
32
+ prop2: 'B',
33
+ },
34
+ isStateful: false,
32
35
  }
33
36
  })
34
37
  } catch (e) {}
@@ -148,7 +151,7 @@ describe('Message handler tests', () => {
148
151
  })
149
152
 
150
153
  it('handles errors for plan', async () => {
151
- const resource= testResource();
154
+ const resource = new TestResource()
152
155
  const plugin = testPlugin(resource);
153
156
 
154
157
  const handler = new MessageHandler(plugin);
@@ -165,7 +168,10 @@ describe('Message handler tests', () => {
165
168
  expect(async () => await handler.onMessage({
166
169
  cmd: 'plan',
167
170
  data: {
168
- type: 'resourceA'
171
+ desired: {
172
+ type: 'resourceA'
173
+ },
174
+ isStateful: false,
169
175
  }
170
176
  })).rejects.to.not.throw;
171
177
 
@@ -173,7 +179,7 @@ describe('Message handler tests', () => {
173
179
  })
174
180
 
175
181
  it('handles errors for apply (create)', async () => {
176
- const resource= testResource();
182
+ const resource = new TestResource()
177
183
  const plugin = testPlugin(resource);
178
184
 
179
185
  const handler = new MessageHandler(plugin);
@@ -182,7 +188,6 @@ describe('Message handler tests', () => {
182
188
  expect(message).toMatchObject({
183
189
  cmd: 'apply_Response',
184
190
  status: MessageStatus.ERROR,
185
- data: 'Create error',
186
191
  })
187
192
  return true;
188
193
  }
@@ -200,7 +205,7 @@ describe('Message handler tests', () => {
200
205
  })
201
206
 
202
207
  it('handles errors for apply (destroy)', async () => {
203
- const resource= testResource();
208
+ const resource = new TestResource()
204
209
  const plugin = testPlugin(resource);
205
210
 
206
211
  const handler = new MessageHandler(plugin);
@@ -209,7 +214,6 @@ describe('Message handler tests', () => {
209
214
  expect(message).toMatchObject({
210
215
  cmd: 'apply_Response',
211
216
  status: MessageStatus.ERROR,
212
- data: 'Destroy error',
213
217
  })
214
218
  return true;
215
219
  }
@@ -225,33 +229,8 @@ describe('Message handler tests', () => {
225
229
  }
226
230
  })).rejects.to.not.throw;
227
231
  })
228
-
229
-
230
- const testResource = () => new class extends Resource<any> {
231
- constructor() {
232
- super({ type: 'resourceA' });
233
- }
234
-
235
- async refresh(keys: Map<keyof any, any>): Promise<Partial<any> | null> {
236
- throw new Error('Refresh error');
237
- }
238
-
239
- applyCreate(plan: Plan<any>): Promise<void> {
240
- throw new Error('Create error');
241
- }
242
-
243
- applyDestroy(plan: Plan<any>): Promise<void> {
244
- throw new Error('Destroy error');
245
- }
246
- }
247
-
248
- const testPlugin = (resource: Resource<any>) => new class extends Plugin {
249
- constructor() {
250
- const map = new Map();
251
- map.set('resourceA', resource);
252
-
253
- super('name', map);
254
- }
255
- }
256
-
257
232
  });
233
+
234
+ function testPlugin(resource: Resource<any>) {
235
+ return Plugin.create('plugin', [resource])
236
+ }
@@ -1,4 +1,4 @@
1
- import { Plugin } from '../entities/plugin.js';
1
+ import { Ajv, SchemaObject, ValidateFunction } from 'ajv';
2
2
  import addFormats from 'ajv-formats';
3
3
  import {
4
4
  ApplyRequestDataSchema,
@@ -14,44 +14,45 @@ import {
14
14
  ValidateRequestDataSchema,
15
15
  ValidateResponseDataSchema
16
16
  } from 'codify-schemas';
17
- import Ajv2020, { SchemaObject, ValidateFunction } from 'ajv/dist/2020.js';
18
- import { SudoError } from '../entities/errors.js';
19
17
 
20
- const SupportedRequests: Record<string, { requestValidator: SchemaObject; responseValidator: SchemaObject; handler: (plugin: Plugin, data: any) => Promise<unknown> }> = {
18
+ import { SudoError } from '../errors.js';
19
+ import { Plugin } from '../plugin/plugin.js';
20
+
21
+ const SupportedRequests: Record<string, { handler: (plugin: Plugin, data: any) => Promise<unknown>; requestValidator: SchemaObject; responseValidator: SchemaObject }> = {
22
+ 'apply': {
23
+ async handler(plugin: Plugin, data: any) {
24
+ await plugin.apply(data);
25
+ return null;
26
+ },
27
+ requestValidator: ApplyRequestDataSchema,
28
+ responseValidator: ApplyResponseDataSchema
29
+ },
21
30
  'initialize': {
31
+ handler: async (plugin: Plugin) => plugin.initialize(),
22
32
  requestValidator: InitializeRequestDataSchema,
23
- responseValidator: InitializeResponseDataSchema,
24
- handler: async (plugin: Plugin) => plugin.initialize()
25
- },
26
- 'validate': {
27
- requestValidator: ValidateRequestDataSchema,
28
- responseValidator: ValidateResponseDataSchema,
29
- handler: async (plugin: Plugin, data: any) => plugin.validate(data)
33
+ responseValidator: InitializeResponseDataSchema
30
34
  },
31
35
  'plan': {
36
+ handler: async (plugin: Plugin, data: any) => plugin.plan(data),
32
37
  requestValidator: PlanRequestDataSchema,
33
- responseValidator: PlanResponseDataSchema,
34
- handler: async (plugin: Plugin, data: any) => plugin.plan(data)
38
+ responseValidator: PlanResponseDataSchema
35
39
  },
36
- 'apply': {
37
- requestValidator: ApplyRequestDataSchema,
38
- responseValidator: ApplyResponseDataSchema,
39
- handler: async (plugin: Plugin, data: any) => {
40
- await plugin.apply(data);
41
- return null;
42
- }
40
+ 'validate': {
41
+ handler: async (plugin: Plugin, data: any) => plugin.validate(data),
42
+ requestValidator: ValidateRequestDataSchema,
43
+ responseValidator: ValidateResponseDataSchema
43
44
  }
44
45
  }
45
46
 
46
47
  export class MessageHandler {
47
- private ajv: Ajv2020.default;
48
+ private ajv: Ajv;
48
49
  private readonly plugin: Plugin;
49
50
  private messageSchemaValidator: ValidateFunction;
50
51
  private requestValidators: Map<string, ValidateFunction>;
51
52
  private responseValidators: Map<string, ValidateFunction>;
52
53
 
53
54
  constructor(plugin: Plugin) {
54
- this.ajv = new Ajv2020.default({ strict: true, strictRequired: false });
55
+ this.ajv = new Ajv({ strict: true, strictRequired: false });
55
56
  addFormats.default(this.ajv);
56
57
  this.ajv.addSchema(ResourceSchema);
57
58
  this.plugin = plugin;
@@ -91,12 +92,12 @@ export class MessageHandler {
91
92
 
92
93
  process.send!({
93
94
  cmd: message.cmd + '_Response',
94
- status: MessageStatus.SUCCESS,
95
95
  data: result,
96
+ status: MessageStatus.SUCCESS,
96
97
  })
97
98
 
98
- } catch (e: unknown) {
99
- this.handleErrors(message, e as Error);
99
+ } catch (error: unknown) {
100
+ this.handleErrors(message, error as Error);
100
101
  }
101
102
  }
102
103
 
@@ -119,8 +120,8 @@ export class MessageHandler {
119
120
  if (e instanceof SudoError) {
120
121
  return process.send?.({
121
122
  cmd,
122
- status: MessageStatus.ERROR,
123
123
  data: `Plugin: '${this.plugin.name}'. Forbidden usage of sudo for command '${e.command}'. Please contact the plugin developer to fix this.`,
124
+ status: MessageStatus.ERROR,
124
125
  })
125
126
  }
126
127
 
@@ -128,8 +129,8 @@ export class MessageHandler {
128
129
 
129
130
  process.send?.({
130
131
  cmd,
131
- status: MessageStatus.ERROR,
132
132
  data: isDebug ? e.stack : e.message,
133
+ status: MessageStatus.ERROR,
133
134
  })
134
135
  }
135
136
  }