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
package/.eslintrc.json CHANGED
@@ -9,13 +9,20 @@
9
9
  "warn",
10
10
  "always"
11
11
  ],
12
- "perfectionist/sort-classes": [
13
- "off"
14
- ],
12
+ "perfectionist/sort-classes": "off",
13
+ "perfectionist/sort-interfaces": "off",
14
+ "perfectionist/sort-enums": "off",
15
+ "perfectionist/sort-objects": "off",
16
+ "perfectionist/sort-object-types": "off",
17
+ "unicorn/no-array-reduce": "off",
18
+ "unicorn/no-array-for-each": "off",
19
+ "unicorn/prefer-object-from-entries": "off",
20
+ "unicorn/prefer-type-error": "off",
15
21
  "quotes": [
16
22
  "error",
17
23
  "single"
18
- ]
24
+ ],
25
+ "no-await-in-loop": "off"
19
26
  },
20
27
  "ignorePatterns": [
21
28
  "*.test.ts"
@@ -0,0 +1,19 @@
1
+ # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3
+
4
+ name: Release
5
+
6
+ on: workflow_dispatch
7
+
8
+ jobs:
9
+ release:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-node@v4
14
+ with:
15
+ node-version: '20.x'
16
+ cache: 'npm'
17
+ - run: npm ci
18
+ - run: tsc
19
+ - run: npm publish
@@ -0,0 +1,19 @@
1
+ # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3
+
4
+ name: Unit tests
5
+
6
+ on: [ push ]
7
+
8
+ jobs:
9
+ build-and-test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-node@v4
14
+ with:
15
+ node-version: '20.x'
16
+ cache: 'npm'
17
+ - run: npm ci
18
+ - run: tsc
19
+ - run: npm run test
@@ -1,6 +1,6 @@
1
- import { Resource } from './resource.js';
2
1
  import { ApplyRequestData, InitializeResponseData, PlanRequestData, PlanResponseData, ResourceConfig, ValidateRequestData, ValidateResponseData } from 'codify-schemas';
3
2
  import { Plan } from './plan.js';
3
+ import { Resource } from './resource.js';
4
4
  export declare class Plugin {
5
5
  name: string;
6
6
  resources: Map<string, Resource<ResourceConfig>>;
@@ -1,5 +1,5 @@
1
- import { Plan } from './plan.js';
2
1
  import { splitUserConfig } from '../utils/utils.js';
2
+ import { Plan } from './plan.js';
3
3
  export class Plugin {
4
4
  name;
5
5
  resources;
@@ -20,8 +20,8 @@ export class Plugin {
20
20
  return {
21
21
  resourceDefinitions: [...this.resources.values()]
22
22
  .map((r) => ({
23
- type: r.typeId,
24
23
  dependencies: r.dependencies,
24
+ type: r.typeId,
25
25
  }))
26
26
  };
27
27
  }
@@ -53,7 +53,7 @@ export class Plugin {
53
53
  }
54
54
  async apply(data) {
55
55
  if (!data.planId && !data.plan) {
56
- throw new Error(`For applies either plan or planId must be supplied`);
56
+ throw new Error('For applies either plan or planId must be supplied');
57
57
  }
58
58
  const plan = this.resolvePlan(data);
59
59
  const resource = this.resources.get(plan.getResourceType());
@@ -63,7 +63,7 @@ export class Plugin {
63
63
  await resource.apply(plan);
64
64
  }
65
65
  resolvePlan(data) {
66
- const { planId, plan: planRequest } = data;
66
+ const { plan: planRequest, planId } = data;
67
67
  if (planId) {
68
68
  if (!this.planStorage.has(planId)) {
69
69
  throw new Error(`Plan with id: ${planId} was not found`);
@@ -74,7 +74,7 @@ export class Plugin {
74
74
  throw new Error('Malformed plan. Resource type must be supplied or resource type was not found');
75
75
  }
76
76
  const resource = this.resources.get(planRequest.resourceType);
77
- return Plan.fromResponse(data.plan, resource.defaultValues);
77
+ return Plan.fromResponse(planRequest, resource.defaultValues);
78
78
  }
79
79
  async crossValidateResources(configs) { }
80
80
  }
@@ -1,22 +1,22 @@
1
1
  import { StringIndexedObject } from 'codify-schemas';
2
+ import { ParameterOptions } from './plan-types.js';
3
+ import { ResourceParameterOptions } from './resource-types.js';
2
4
  import { StatefulParameter } from './stateful-parameter.js';
3
5
  import { TransformParameter } from './transform-parameter.js';
4
- import { ResourceParameterOptions } from './resource-types.js';
5
- import { ParameterOptions } from './plan-types.js';
6
6
  export interface ResourceOptions<T extends StringIndexedObject> {
7
- type: string;
8
- schema?: unknown;
9
7
  callStatefulParameterRemoveOnDestroy?: boolean;
10
8
  dependencies?: string[];
11
9
  parameterOptions?: Partial<Record<keyof T, ResourceParameterOptions | ResourceStatefulParameterOptions<T> | ResourceTransformParameterOptions<T>>>;
10
+ schema?: unknown;
11
+ type: string;
12
12
  }
13
13
  export interface ResourceStatefulParameterOptions<T extends StringIndexedObject> {
14
- statefulParameter: StatefulParameter<T, T[keyof T]>;
15
14
  order?: number;
15
+ statefulParameter: StatefulParameter<T, T[keyof T]>;
16
16
  }
17
17
  export interface ResourceTransformParameterOptions<T extends StringIndexedObject> {
18
- transformParameter: TransformParameter<T>;
19
18
  order?: number;
19
+ transformParameter: TransformParameter<T>;
20
20
  }
21
21
  export declare class ResourceOptionsParser<T extends StringIndexedObject> {
22
22
  private options;
@@ -27,15 +27,13 @@ export class ResourceOptionsParser {
27
27
  const resourceParameters = Object.fromEntries([...this.resourceParameters.entries()]
28
28
  .map(([name, value]) => ([name, { ...value, isStatefulParameter: false }])));
29
29
  const statefulParameters = [...this.statefulParameters.entries()]
30
- ?.reduce((obj, sp) => {
31
- return {
32
- ...obj,
33
- [sp[0]]: {
34
- ...sp[1].options,
35
- isStatefulParameter: true,
36
- }
37
- };
38
- }, {});
30
+ ?.reduce((obj, sp) => ({
31
+ ...obj,
32
+ [sp[0]]: {
33
+ ...sp[1].options,
34
+ isStatefulParameter: true,
35
+ }
36
+ }), {});
39
37
  return {
40
38
  ...resourceParameters,
41
39
  ...statefulParameters,
@@ -1,5 +1,4 @@
1
- import Ajv from 'ajv';
2
- import { ValidateFunction } from 'ajv/dist/2020.js';
1
+ import { Ajv, ValidateFunction } from 'ajv';
3
2
  import { ResourceConfig, StringIndexedObject, ValidateResponseData } from 'codify-schemas';
4
3
  import { ParameterChange } from './change-set.js';
5
4
  import { Plan } from './plan.js';
@@ -19,7 +18,7 @@ export declare abstract class Resource<T extends StringIndexedObject> {
19
18
  readonly parameterOptions: Record<keyof T, ParameterOptions>;
20
19
  readonly options: ResourceOptions<T>;
21
20
  readonly defaultValues: Partial<Record<keyof T, unknown>>;
22
- protected ajv?: Ajv.default;
21
+ protected ajv?: Ajv;
23
22
  protected schemaValidator?: ValidateFunction;
24
23
  protected constructor(options: ResourceOptions<T>);
25
24
  onInitialize(): Promise<void>;
@@ -1,4 +1,4 @@
1
- import Ajv2020 from 'ajv/dist/2020.js';
1
+ import { Ajv } from 'ajv';
2
2
  import { ParameterOperation, ResourceOperation, } from 'codify-schemas';
3
3
  import { setsEqual, splitUserConfig } from '../utils/utils.js';
4
4
  import { Plan } from './plan.js';
@@ -21,7 +21,7 @@ export class Resource {
21
21
  this.dependencies = options.dependencies ?? [];
22
22
  this.options = options;
23
23
  if (this.options.schema) {
24
- this.ajv = new Ajv2020.default({
24
+ this.ajv = new Ajv({
25
25
  allErrors: true,
26
26
  strict: true,
27
27
  strictRequired: false,
@@ -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
+ }
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { Plugin } from './entities/plugin.js';
2
- export * from './entities/resource.js';
3
- export * from './entities/resource-types.js';
4
- export * from './entities/resource-options.js';
5
- export * from './entities/plugin.js';
6
- export * from './entities/change-set.js';
7
- export * from './entities/plan.js';
8
- export * from './entities/plan-types.js';
9
- export * from './entities/stateful-parameter.js';
10
- export * from './entities/errors.js';
1
+ import { Plugin } from './plugin/plugin.js';
2
+ export * from './errors.js';
3
+ export * from './plan/change-set.js';
4
+ export * from './plan/plan.js';
5
+ export * from './plan/plan-types.js';
6
+ export * from './plugin/plugin.js';
7
+ export * from './resource/parsed-resource-settings.js';
8
+ export * from './resource/resource.js';
9
+ export * from './resource/resource-settings.js';
10
+ export * from './resource/stateful-parameter.js';
11
11
  export * from './utils/utils.js';
12
12
  export declare function runPlugin(plugin: Plugin): Promise<void>;
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { MessageHandler } from './messages/handlers.js';
2
- export * from './entities/resource.js';
3
- export * from './entities/resource-types.js';
4
- export * from './entities/resource-options.js';
5
- export * from './entities/plugin.js';
6
- export * from './entities/change-set.js';
7
- export * from './entities/plan.js';
8
- export * from './entities/plan-types.js';
9
- export * from './entities/stateful-parameter.js';
10
- export * from './entities/errors.js';
2
+ export * from './errors.js';
3
+ export * from './plan/change-set.js';
4
+ export * from './plan/plan.js';
5
+ export * from './plan/plan-types.js';
6
+ export * from './plugin/plugin.js';
7
+ export * from './resource/parsed-resource-settings.js';
8
+ export * from './resource/resource.js';
9
+ export * from './resource/resource-settings.js';
10
+ export * from './resource/stateful-parameter.js';
11
11
  export * from './utils/utils.js';
12
12
  export async function runPlugin(plugin) {
13
13
  const messageHandler = new MessageHandler(plugin);
@@ -1,4 +1,4 @@
1
- import { Plugin } from '../entities/plugin.js';
1
+ import { Plugin } from '../plugin/plugin.js';
2
2
  export declare class MessageHandler {
3
3
  private ajv;
4
4
  private readonly plugin;
@@ -1,30 +1,30 @@
1
+ import { Ajv } from 'ajv';
1
2
  import addFormats from 'ajv-formats';
2
3
  import { ApplyRequestDataSchema, ApplyResponseDataSchema, InitializeRequestDataSchema, InitializeResponseDataSchema, IpcMessageSchema, MessageStatus, PlanRequestDataSchema, PlanResponseDataSchema, ResourceSchema, ValidateRequestDataSchema, ValidateResponseDataSchema } from 'codify-schemas';
3
- import Ajv2020 from 'ajv/dist/2020.js';
4
- import { SudoError } from '../entities/errors.js';
4
+ import { SudoError } from '../errors.js';
5
5
  const SupportedRequests = {
6
+ 'apply': {
7
+ async handler(plugin, data) {
8
+ await plugin.apply(data);
9
+ return null;
10
+ },
11
+ requestValidator: ApplyRequestDataSchema,
12
+ responseValidator: ApplyResponseDataSchema
13
+ },
6
14
  'initialize': {
15
+ handler: async (plugin) => plugin.initialize(),
7
16
  requestValidator: InitializeRequestDataSchema,
8
- responseValidator: InitializeResponseDataSchema,
9
- handler: async (plugin) => plugin.initialize()
10
- },
11
- 'validate': {
12
- requestValidator: ValidateRequestDataSchema,
13
- responseValidator: ValidateResponseDataSchema,
14
- handler: async (plugin, data) => plugin.validate(data)
17
+ responseValidator: InitializeResponseDataSchema
15
18
  },
16
19
  'plan': {
20
+ handler: async (plugin, data) => plugin.plan(data),
17
21
  requestValidator: PlanRequestDataSchema,
18
- responseValidator: PlanResponseDataSchema,
19
- handler: async (plugin, data) => plugin.plan(data)
22
+ responseValidator: PlanResponseDataSchema
20
23
  },
21
- 'apply': {
22
- requestValidator: ApplyRequestDataSchema,
23
- responseValidator: ApplyResponseDataSchema,
24
- handler: async (plugin, data) => {
25
- await plugin.apply(data);
26
- return null;
27
- }
24
+ 'validate': {
25
+ handler: async (plugin, data) => plugin.validate(data),
26
+ requestValidator: ValidateRequestDataSchema,
27
+ responseValidator: ValidateResponseDataSchema
28
28
  }
29
29
  };
30
30
  export class MessageHandler {
@@ -34,7 +34,7 @@ export class MessageHandler {
34
34
  requestValidators;
35
35
  responseValidators;
36
36
  constructor(plugin) {
37
- this.ajv = new Ajv2020.default({ strict: true, strictRequired: false });
37
+ this.ajv = new Ajv({ strict: true, strictRequired: false });
38
38
  addFormats.default(this.ajv);
39
39
  this.ajv.addSchema(ResourceSchema);
40
40
  this.plugin = plugin;
@@ -63,12 +63,12 @@ export class MessageHandler {
63
63
  }
64
64
  process.send({
65
65
  cmd: message.cmd + '_Response',
66
- status: MessageStatus.SUCCESS,
67
66
  data: result,
67
+ status: MessageStatus.SUCCESS,
68
68
  });
69
69
  }
70
- catch (e) {
71
- this.handleErrors(message, e);
70
+ catch (error) {
71
+ this.handleErrors(message, error);
72
72
  }
73
73
  }
74
74
  validateMessage(message) {
@@ -81,19 +81,20 @@ export class MessageHandler {
81
81
  if (!message.hasOwnProperty('cmd')) {
82
82
  return;
83
83
  }
84
+ // @ts-ignore
84
85
  const cmd = message.cmd + '_Response';
85
86
  if (e instanceof SudoError) {
86
87
  return process.send?.({
87
88
  cmd,
88
- status: MessageStatus.ERROR,
89
89
  data: `Plugin: '${this.plugin.name}'. Forbidden usage of sudo for command '${e.command}'. Please contact the plugin developer to fix this.`,
90
+ status: MessageStatus.ERROR,
90
91
  });
91
92
  }
92
93
  const isDebug = process.env.DEBUG?.includes('*') ?? false;
93
94
  process.send?.({
94
95
  cmd,
95
- status: MessageStatus.ERROR,
96
96
  data: isDebug ? e.stack : e.message,
97
+ status: MessageStatus.ERROR,
97
98
  });
98
99
  }
99
100
  }
@@ -0,0 +1,37 @@
1
+ import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
2
+ import { ParameterSetting } from '../resource/resource-settings.js';
3
+ /**
4
+ * A parameter change describes a parameter level change to a resource.
5
+ */
6
+ export interface ParameterChange<T extends StringIndexedObject> {
7
+ /**
8
+ * The name of the parameter
9
+ */
10
+ name: keyof T & string;
11
+ /**
12
+ * The operation to be performed on the parameter.
13
+ */
14
+ operation: ParameterOperation;
15
+ /**
16
+ * The previous value of the resource (the current value on the system)
17
+ */
18
+ previousValue: any | null;
19
+ /**
20
+ * The new value of the resource (the desired value)
21
+ */
22
+ newValue: any | null;
23
+ }
24
+ export declare class ChangeSet<T extends StringIndexedObject> {
25
+ operation: ResourceOperation;
26
+ parameterChanges: Array<ParameterChange<T>>;
27
+ constructor(operation: ResourceOperation, parameterChanges: Array<ParameterChange<T>>);
28
+ get desiredParameters(): T;
29
+ get currentParameters(): T;
30
+ static empty<T extends StringIndexedObject>(): ChangeSet<T>;
31
+ static create<T extends StringIndexedObject>(desired: Partial<T>): ChangeSet<T>;
32
+ static destroy<T extends StringIndexedObject>(current: Partial<T>): ChangeSet<T>;
33
+ static calculateModification<T extends StringIndexedObject>(desired: Partial<T>, current: Partial<T>, parameterSettings?: Partial<Record<keyof T, ParameterSetting>>): ChangeSet<T>;
34
+ private static calculateParameterChanges;
35
+ private static combineResourceOperations;
36
+ private static isSame;
37
+ }
@@ -0,0 +1,146 @@
1
+ import { ParameterOperation, ResourceOperation } from 'codify-schemas';
2
+ import { areArraysEqual } from '../utils/utils.js';
3
+ // Change set will coerce undefined values to null because undefined is not valid JSON
4
+ export class ChangeSet {
5
+ operation;
6
+ parameterChanges;
7
+ constructor(operation, parameterChanges) {
8
+ this.operation = operation;
9
+ this.parameterChanges = parameterChanges;
10
+ }
11
+ get desiredParameters() {
12
+ return this.parameterChanges
13
+ .reduce((obj, pc) => ({
14
+ ...obj,
15
+ [pc.name]: pc.newValue,
16
+ }), {});
17
+ }
18
+ get currentParameters() {
19
+ return this.parameterChanges
20
+ .reduce((obj, pc) => ({
21
+ ...obj,
22
+ [pc.name]: pc.previousValue,
23
+ }), {});
24
+ }
25
+ static empty() {
26
+ return new ChangeSet(ResourceOperation.NOOP, []);
27
+ }
28
+ static create(desired) {
29
+ const parameterChanges = Object.entries(desired)
30
+ .map(([k, v]) => ({
31
+ name: k,
32
+ operation: ParameterOperation.ADD,
33
+ previousValue: null,
34
+ newValue: v ?? null,
35
+ }));
36
+ return new ChangeSet(ResourceOperation.CREATE, parameterChanges);
37
+ }
38
+ static destroy(current) {
39
+ const parameterChanges = Object.entries(current)
40
+ .map(([k, v]) => ({
41
+ name: k,
42
+ operation: ParameterOperation.REMOVE,
43
+ previousValue: v ?? null,
44
+ newValue: null,
45
+ }));
46
+ return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
47
+ }
48
+ static calculateModification(desired, current, parameterSettings = {}) {
49
+ const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
50
+ const statefulParameterKeys = new Set(Object.entries(parameterSettings)
51
+ .filter(([, v]) => v?.type === 'stateful')
52
+ .map(([k]) => k));
53
+ const resourceOperation = pc
54
+ .filter((change) => change.operation !== ParameterOperation.NOOP)
55
+ .reduce((operation, curr) => {
56
+ let newOperation;
57
+ if (statefulParameterKeys.has(curr.name)) {
58
+ newOperation = ResourceOperation.MODIFY; // All stateful parameters are modify only
59
+ }
60
+ else if (parameterSettings[curr.name]?.canModify) {
61
+ newOperation = ResourceOperation.MODIFY;
62
+ }
63
+ else {
64
+ newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
65
+ }
66
+ return ChangeSet.combineResourceOperations(operation, newOperation);
67
+ }, ResourceOperation.NOOP);
68
+ return new ChangeSet(resourceOperation, pc);
69
+ }
70
+ static calculateParameterChanges(desiredParameters, currentParameters, parameterOptions) {
71
+ const parameterChangeSet = new Array();
72
+ // Filter out null and undefined values or else the diff below will not work
73
+ const desired = Object.fromEntries(Object.entries(desiredParameters).filter(([, v]) => v !== null && v !== undefined));
74
+ const current = Object.fromEntries(Object.entries(currentParameters).filter(([, v]) => v !== null && v !== undefined));
75
+ for (const [k, v] of Object.entries(current)) {
76
+ if (desired?.[k] === null || desired?.[k] === undefined) {
77
+ parameterChangeSet.push({
78
+ name: k,
79
+ previousValue: v ?? null,
80
+ newValue: null,
81
+ operation: ParameterOperation.REMOVE,
82
+ });
83
+ delete current[k];
84
+ continue;
85
+ }
86
+ if (!ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
87
+ parameterChangeSet.push({
88
+ name: k,
89
+ previousValue: v ?? null,
90
+ newValue: desired[k] ?? null,
91
+ operation: ParameterOperation.MODIFY,
92
+ });
93
+ delete current[k];
94
+ delete desired[k];
95
+ continue;
96
+ }
97
+ parameterChangeSet.push({
98
+ name: k,
99
+ previousValue: v ?? null,
100
+ newValue: desired[k] ?? null,
101
+ operation: ParameterOperation.NOOP,
102
+ });
103
+ delete current[k];
104
+ delete desired[k];
105
+ }
106
+ if (Object.keys(current).length > 0) {
107
+ throw new Error('Diff algorithm error');
108
+ }
109
+ for (const [k, v] of Object.entries(desired)) {
110
+ parameterChangeSet.push({
111
+ name: k,
112
+ previousValue: null,
113
+ newValue: v ?? null,
114
+ operation: ParameterOperation.ADD,
115
+ });
116
+ }
117
+ return parameterChangeSet;
118
+ }
119
+ static combineResourceOperations(prev, next) {
120
+ const orderOfOperations = [
121
+ ResourceOperation.NOOP,
122
+ ResourceOperation.MODIFY,
123
+ ResourceOperation.RECREATE,
124
+ ResourceOperation.CREATE,
125
+ ResourceOperation.DESTROY,
126
+ ];
127
+ const indexPrev = orderOfOperations.indexOf(prev);
128
+ const indexNext = orderOfOperations.indexOf(next);
129
+ return orderOfOperations[Math.max(indexPrev, indexNext)];
130
+ }
131
+ static isSame(desired, current, setting) {
132
+ switch (setting?.type) {
133
+ case 'stateful': {
134
+ const statefulSetting = setting.definition.getSettings();
135
+ return ChangeSet.isSame(desired, current, statefulSetting);
136
+ }
137
+ case 'array': {
138
+ const arrayParameter = setting;
139
+ return areArraysEqual(arrayParameter, desired, current);
140
+ }
141
+ default: {
142
+ return (setting?.isEqual ?? ((a, b) => a === b))(desired, current);
143
+ }
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,23 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ import { Plan } from './plan.js';
3
+ /**
4
+ * A narrower type for plans for CREATE operations. Only desiredConfig is not null.
5
+ */
6
+ export interface CreatePlan<T extends StringIndexedObject> extends Plan<T> {
7
+ desiredConfig: T;
8
+ currentConfig: null;
9
+ }
10
+ /**
11
+ * A narrower type for plans for DESTROY operations. Only currentConfig is not null.
12
+ */
13
+ export interface DestroyPlan<T extends StringIndexedObject> extends Plan<T> {
14
+ desiredConfig: null;
15
+ currentConfig: T;
16
+ }
17
+ /**
18
+ * A narrower type for plans for MODIFY and RE-CREATE operations.
19
+ */
20
+ export interface ModifyPlan<T extends StringIndexedObject> extends Plan<T> {
21
+ desiredConfig: T;
22
+ currentConfig: T;
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { ApplyRequestData, PlanResponseData, ResourceConfig, StringIndexedObject } from 'codify-schemas';
2
+ import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
3
+ import { ChangeSet } from './change-set.js';
4
+ /**
5
+ * A plan represents a set of actions that after taken will turn the current resource into the desired one.
6
+ * A plan consists of list of parameter level changes (ADD, REMOVE, MODIFY or NO-OP) as well as a resource level
7
+ * operation (CREATE, DESTROY, MODIFY, RE-CREATE, NO-OP).
8
+ */
9
+ export declare class Plan<T extends StringIndexedObject> {
10
+ id: string;
11
+ changeSet: ChangeSet<T>;
12
+ coreParameters: ResourceConfig;
13
+ constructor(id: string, changeSet: ChangeSet<T>, resourceMetadata: ResourceConfig);
14
+ /**
15
+ * The desired config that a plan will achieve after executing all the actions.
16
+ */
17
+ get desiredConfig(): T | null;
18
+ /**
19
+ * The current config that the plan is changing.
20
+ */
21
+ get currentConfig(): T | null;
22
+ /**
23
+ * When multiples of the same resource are allowed, this matching function will match a given config with one of the
24
+ * existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
25
+ * the application name and location to match it to our desired configs name and location.
26
+ *
27
+ * @param params
28
+ * @private
29
+ */
30
+ private static matchCurrentParameters;
31
+ /**
32
+ * The type (id) of the resource
33
+ *
34
+ * @return string
35
+ */
36
+ getResourceType(): string;
37
+ static calculate<T extends StringIndexedObject>(params: {
38
+ desiredParameters: Partial<T> | null;
39
+ currentParametersArray: Partial<T>[] | null;
40
+ stateParameters: Partial<T> | null;
41
+ coreParameters: ResourceConfig;
42
+ settings: ParsedResourceSettings<T>;
43
+ statefulMode: boolean;
44
+ }): Plan<T>;
45
+ /**
46
+ * Only keep relevant params for the plan. We don't want to change settings that were not already
47
+ * defined.
48
+ *
49
+ * 1. In stateless mode, filter current by desired. We only want to know about settings that the user has specified
50
+ * 2. In stateful mode, filter current by state and desired. We only know about the settings the user has previously set
51
+ * or wants to set. If a parameter is not specified then it's not managed by Codify.
52
+ */
53
+ private static filterCurrentParams;
54
+ static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T>;
55
+ /**
56
+ * Convert the plan to a JSON response object
57
+ */
58
+ toResponse(): PlanResponseData;
59
+ }