codify-plugin-lib 1.0.76 → 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 (70) 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/errors.d.ts +4 -0
  5. package/dist/errors.js +7 -0
  6. package/dist/index.d.ts +10 -10
  7. package/dist/index.js +9 -9
  8. package/dist/messages/handlers.d.ts +1 -1
  9. package/dist/messages/handlers.js +2 -1
  10. package/dist/plan/change-set.d.ts +37 -0
  11. package/dist/plan/change-set.js +146 -0
  12. package/dist/plan/plan-types.d.ts +23 -0
  13. package/dist/plan/plan-types.js +1 -0
  14. package/dist/plan/plan.d.ts +59 -0
  15. package/dist/plan/plan.js +228 -0
  16. package/dist/plugin/plugin.d.ts +17 -0
  17. package/dist/plugin/plugin.js +83 -0
  18. package/dist/resource/config-parser.d.ts +14 -0
  19. package/dist/resource/config-parser.js +48 -0
  20. package/dist/resource/parsed-resource-settings.d.ts +26 -0
  21. package/dist/resource/parsed-resource-settings.js +126 -0
  22. package/dist/resource/resource-controller.d.ts +30 -0
  23. package/dist/resource/resource-controller.js +247 -0
  24. package/dist/resource/resource-settings.d.ts +149 -0
  25. package/dist/resource/resource-settings.js +9 -0
  26. package/dist/resource/resource.d.ts +137 -0
  27. package/dist/resource/resource.js +44 -0
  28. package/dist/resource/stateful-parameter.d.ts +164 -0
  29. package/dist/resource/stateful-parameter.js +94 -0
  30. package/dist/utils/utils.d.ts +19 -3
  31. package/dist/utils/utils.js +52 -3
  32. package/package.json +5 -3
  33. package/src/index.ts +10 -11
  34. package/src/messages/handlers.test.ts +10 -37
  35. package/src/messages/handlers.ts +2 -2
  36. package/src/plan/change-set.test.ts +220 -0
  37. package/src/plan/change-set.ts +225 -0
  38. package/src/plan/plan-types.ts +27 -0
  39. package/src/{entities → plan}/plan.test.ts +35 -29
  40. package/src/plan/plan.ts +353 -0
  41. package/src/{entities → plugin}/plugin.test.ts +14 -13
  42. package/src/{entities → plugin}/plugin.ts +28 -24
  43. package/src/resource/config-parser.ts +77 -0
  44. package/src/{entities/resource-options.test.ts → resource/parsed-resource-settings.test.ts} +8 -7
  45. package/src/resource/parsed-resource-settings.ts +179 -0
  46. package/src/{entities/resource-stateful-mode.test.ts → resource/resource-controller-stateful-mode.test.ts} +36 -39
  47. package/src/{entities/resource.test.ts → resource/resource-controller.test.ts} +116 -176
  48. package/src/resource/resource-controller.ts +340 -0
  49. package/src/resource/resource-settings.test.ts +494 -0
  50. package/src/resource/resource-settings.ts +192 -0
  51. package/src/resource/resource.ts +149 -0
  52. package/src/resource/stateful-parameter.test.ts +93 -0
  53. package/src/resource/stateful-parameter.ts +217 -0
  54. package/src/utils/test-utils.test.ts +87 -0
  55. package/src/utils/utils.test.ts +2 -2
  56. package/src/utils/utils.ts +51 -5
  57. package/tsconfig.json +0 -1
  58. package/vitest.config.ts +10 -0
  59. package/src/entities/change-set.test.ts +0 -155
  60. package/src/entities/change-set.ts +0 -244
  61. package/src/entities/plan-types.ts +0 -44
  62. package/src/entities/plan.ts +0 -178
  63. package/src/entities/resource-options.ts +0 -155
  64. package/src/entities/resource-parameters.test.ts +0 -604
  65. package/src/entities/resource-types.ts +0 -31
  66. package/src/entities/resource.ts +0 -470
  67. package/src/entities/stateful-parameter.test.ts +0 -114
  68. package/src/entities/stateful-parameter.ts +0 -92
  69. package/src/entities/transform-parameter.ts +0 -13
  70. /package/src/{entities/errors.ts → errors.ts} +0 -0
@@ -0,0 +1,149 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ import { StatefulParameter } from './stateful-parameter.js';
3
+ /**
4
+ * The configuration and settings for a resource.
5
+ */
6
+ export interface ResourceSettings<T extends StringIndexedObject> {
7
+ /**
8
+ * The typeId of the resource.
9
+ */
10
+ id: string;
11
+ /**
12
+ * Schema to validate user configs with. Must be in the format JSON Schema draft07
13
+ */
14
+ schema?: unknown;
15
+ /**
16
+ * Allow multiple of the same resource to unique. Set truthy if
17
+ * multiples are allowed, for example for applications, there can be multiple copy of the same application installed
18
+ * on the system. Or there can be multiple git repos. Defaults to false.
19
+ */
20
+ allowMultiple?: {
21
+ /**
22
+ * If multiple copies are allowed then a matcher must be defined to match the desired
23
+ * config with one of the resources currently existing on the system. Return null if there is no match.
24
+ *
25
+ * @param current An array of resources found installed on the system
26
+ * @param desired The desired config to match.
27
+ *
28
+ * @return The matched resource.
29
+ */
30
+ matcher: (desired: Partial<T>, current: Partial<T>[]) => Partial<T>;
31
+ };
32
+ /**
33
+ * If true, {@link StatefulParameter} remove() will be called before resource destruction. This is useful
34
+ * if the stateful parameter needs to be first uninstalled (cleanup) before the overall resource can be
35
+ * uninstalled. Defaults to false.
36
+ */
37
+ removeStatefulParametersBeforeDestroy?: boolean;
38
+ /**
39
+ * An array of type ids of resources that this resource depends on. This affects the order in which multiple resources are
40
+ * planned and applied.
41
+ */
42
+ dependencies?: string[];
43
+ /**
44
+ * Options for configuring parameters operations including overriding the equals function, adding default values
45
+ * and applying any input transformations. Use parameter settings to define stateful parameters as well.
46
+ */
47
+ parameterSettings?: Partial<Record<keyof T, ParameterSetting>>;
48
+ /**
49
+ * A config level transformation that is only applied to the user supplied desired config. This transformation is allowed
50
+ * to add, remove or modify keys as well as values. Changing this transformation for existing libraries will mess up existing states.
51
+ *
52
+ * @param desired
53
+ */
54
+ inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
55
+ }
56
+ /**
57
+ * The type of parameter. This value is mainly used to determine a pre-set equality method for comparing the current
58
+ * config with desired config. Certain types will have additional options to help support it. For example the type
59
+ * stateful requires a stateful parameter definition and type array takes an isElementEqual method.
60
+ */
61
+ export type ParameterSettingType = 'any' | 'array' | 'boolean' | 'directory' | 'number' | 'stateful' | 'string' | 'version';
62
+ /**
63
+ * Typing information for the parameter setting. This represents a setting on a specific parameter within a
64
+ * resource. Options for configuring parameters operations including overriding the equals function, adding default values
65
+ * and applying any input transformations. See {@link DefaultParameterSetting } for more information.
66
+ * Use parameter settings to define stateful parameters as well.
67
+ */
68
+ export type ParameterSetting = ArrayParameterSetting | DefaultParameterSetting | StatefulParameterSetting;
69
+ /**
70
+ * The parent class for parameter settings. The options are applicable to array parameter settings
71
+ * as well.
72
+ */
73
+ export interface DefaultParameterSetting {
74
+ /**
75
+ * The type of the value of this parameter. See {@link ParameterSettingType} for the available options. This value
76
+ * is mainly used to determine the equality method when performing diffing.
77
+ */
78
+ type?: ParameterSettingType;
79
+ /**
80
+ * Default value for the parameter. If a value is not provided in the config, then this value will be used.
81
+ */
82
+ default?: unknown;
83
+ /**
84
+ * A transformation of the input value for this parameter. This transformation is only applied to the desired parameter
85
+ * value supplied by the user.
86
+ *
87
+ * @param input The original parameter value from the desired config.
88
+ */
89
+ inputTransformation?: (input: any) => Promise<any> | unknown;
90
+ /**
91
+ * Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
92
+ * This value will override the pre-set equality function from the type. Return true if the desired value is
93
+ * equivalent to the current value.
94
+ *
95
+ * @param desired The desired value.
96
+ * @param current The current value.
97
+ *
98
+ * @return Return true if equal
99
+ */
100
+ isEqual?: (desired: any, current: any) => boolean;
101
+ /**
102
+ * Chose if the resource can be modified instead of re-created when there is a change to this parameter.
103
+ * Defaults to false (re-create).
104
+ *
105
+ * Examples:
106
+ * 1. Settings like git user name and git user email that have setter calls and don't require the re-installation of git
107
+ * 2. AWS profile secret keys that can be updated without the re-installation of AWS CLI
108
+ */
109
+ canModify?: boolean;
110
+ }
111
+ /**
112
+ * Array type specific settings. See {@link DefaultParameterSetting } for a full list of options.
113
+ */
114
+ export interface ArrayParameterSetting extends DefaultParameterSetting {
115
+ type: 'array';
116
+ /**
117
+ * An element level equality function for arrays. The diffing algorithm will take isElementEqual and use it in a
118
+ * O(n^2) equality comparison to determine if the overall array is equal. This value will override the pre-set equality
119
+ * function for arrays (desired === current). Return true if the desired element is equivalent to the current element.
120
+ *
121
+ * @param desired An element of the desired array
122
+ * @param current An element of the current array
123
+ *
124
+ * @return Return true if desired is equivalent to current.
125
+ */
126
+ isElementEqual?: (desired: any, current: any) => boolean;
127
+ }
128
+ /**
129
+ * Stateful parameter type specific settings. A stateful parameter is a sub-resource that can hold its own
130
+ * state but is still tied to the overall state of the resource. For example 'homebrew' is represented
131
+ * as a resource and taps, formulas and casks are represented as a stateful parameter. A formula can be installed,
132
+ * modified and removed (has state) but it is still tied to the overall lifecycle of homebrew.
133
+ *
134
+ */
135
+ export interface StatefulParameterSetting extends DefaultParameterSetting {
136
+ type: 'stateful';
137
+ /**
138
+ * The stateful parameter definition. A stateful parameter is a sub-resource that can hold its own
139
+ * state but is still tied to the overall state of the resource. For example 'homebrew' is represented
140
+ * as a resource and taps, formulas and casks are represented as a stateful parameter. A formula can be installed,
141
+ * modified and removed (has state) but it is still tied to the overall lifecycle of homebrew.
142
+ */
143
+ definition: StatefulParameter<any, unknown>;
144
+ /**
145
+ * The order multiple stateful parameters should be applied in. The order is applied in ascending order (1, 2, 3...).
146
+ */
147
+ order?: number;
148
+ }
149
+ export declare const ParameterEqualsDefaults: Partial<Record<ParameterSettingType, (a: unknown, b: unknown) => boolean>>;
@@ -0,0 +1,9 @@
1
+ import path from 'node:path';
2
+ import { untildify } from '../utils/utils.js';
3
+ export const ParameterEqualsDefaults = {
4
+ 'boolean': (a, b) => Boolean(a) === Boolean(b),
5
+ 'directory': (a, b) => path.resolve(untildify(String(a))) === path.resolve(untildify(String(b))),
6
+ 'number': (a, b) => Number(a) === Number(b),
7
+ 'string': (a, b) => String(a) === String(b),
8
+ 'version': (desired, current) => String(current).includes(String(desired))
9
+ };
@@ -0,0 +1,137 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ import { ParameterChange } from '../plan/change-set.js';
3
+ import { CreatePlan, DestroyPlan, ModifyPlan } from '../plan/plan-types.js';
4
+ import { ResourceSettings } from './resource-settings.js';
5
+ /**
6
+ * A resource represents an object on the system (application, CLI tool, or setting)
7
+ * that has state and can be created and destroyed. Examples of resources include CLI tools
8
+ * like homebrew, docker, and xcode-tools; applications like Google Chrome, Zoom, and OpenVPN;
9
+ * and settings like AWS profiles, git configs and system preference settings.
10
+ */
11
+ export declare abstract class Resource<T extends StringIndexedObject> {
12
+ /**
13
+ * Return the settings for the resource. Consult the typing for {@link ResourceSettings} for
14
+ * a description of the options.
15
+ *
16
+ * **Parameters**:
17
+ * - id: The id of the resource. This translates to the `type` id parameter in codify.json configs
18
+ * - schema: A JSON schema used to validate user input
19
+ * - allowMultiple: Allow multiple copies of the resource to exist at the same time. If true then,
20
+ * a matcher must be defined that matches a user defined config and a single resource on the system.
21
+ * - removeStatefulParametersBeforeDestory: Call the delete methods of stateful parameters before destorying
22
+ * the base resource. Defaults to false.
23
+ * - dependencies: Specify the ids of any resources that this resource depends on
24
+ * - parameterSettings: Parameter specific settings. Use this to define custom equals functions, default values
25
+ * and input transformations
26
+ * - inputTransformation: Transform the input value.
27
+ *
28
+ * @return ResourceSettings The resource settings
29
+ */
30
+ abstract getSettings(): ResourceSettings<T>;
31
+ initialize(): Promise<void>;
32
+ /**
33
+ * Add custom validation logic in-addition to the default schema validation.
34
+ * In this method throw an error if the object did not validate. The message of the
35
+ * error will be shown to the user.
36
+ * @param parameters
37
+ */
38
+ validate(parameters: Partial<T>): Promise<void>;
39
+ /**
40
+ * Return the status of the resource on the system. If multiple resources exist, then return all instances of
41
+ * the resource back. Query for the individual parameters specified in the parameter param.
42
+ * Return null if the resource does not exist.
43
+ *
44
+ * Example (Android Studios Resource):
45
+ * 1. Receive Input:
46
+ * ```
47
+ * {
48
+ * name: 'Android Studios.app'
49
+ * directory: '/Application',
50
+ * version: '2023.2'
51
+ * }
52
+ * ```
53
+ * 2. Query the system for any installed Android studio versions.
54
+ * 3. In this example we find that there is an 2023.2 version installed and an
55
+ * additional 2024.3-beta version installed as well.
56
+ * 4. We would return:
57
+ * ```
58
+ * [
59
+ * { name: 'Android Studios.app', directory: '/Application', version: '2023.2' },
60
+ * { name: 'Android Studios Preview.app', directory: '/Application', version: '2024.3' },
61
+ * ]
62
+ * ```
63
+ *
64
+ * @param parameters The parameters to refresh. In stateless mode this will be the parameters
65
+ * of the desired config. In stateful mode, this will be parameters of the state config + the desired
66
+ * config of any new parameters.
67
+ *
68
+ * @return A config or an array of configs representing the status of the resource on the
69
+ * system currently
70
+ */
71
+ abstract refresh(parameters: Partial<T>): Promise<Array<Partial<T>> | Partial<T> | null>;
72
+ /**
73
+ * Create the resource (install) based on the parameters passed in. Only the desired parameters will
74
+ * be non-null because in a CREATE plan, the current value is null.
75
+ *
76
+ * Example (Android Studios Resource):
77
+ * 1. We receive a plan of:
78
+ * ```
79
+ * Plan {
80
+ * desiredConfig: {
81
+ * name: 'Android Studios.app',
82
+ * directory: '/Application',
83
+ * version: '2023.2'
84
+ * }
85
+ * currentConfig: null,
86
+ * }
87
+ * ```
88
+ * 2. Install version Android Studios 2023.2 and then return.
89
+ *
90
+ * @param plan The plan of what to install. Use only the desiredConfig because currentConfig is null.
91
+ */
92
+ abstract create(plan: CreatePlan<T>): Promise<void>;
93
+ /**
94
+ * Modify a single parameter of a resource. Modify is optional to override and is only called
95
+ * when a resourceSetting was set to `canModify = true`. This method should only modify
96
+ * a single parameter at a time as specified by the first parameter: ParameterChange.
97
+ *
98
+ * Example (AWS Profile Resource):
99
+ * 1. We receive a parameter change of:
100
+ * ```
101
+ * {
102
+ * name: 'awsAccessKeyId',
103
+ * operation: ParameterOperation.MODIFY,
104
+ * newValue: '123456',
105
+ * previousValue: 'abcdef'
106
+ * }
107
+ * ```
108
+ * 2. Use an if statement to only apply this operation for the parameter `awsAccessKeyId`
109
+ * 3. Update the value of the `aws_access_key_id` to the `newValue` specified in the parameter change
110
+ *
111
+ * @param pc ParameterChange, the parameter name and values to modify on the resource
112
+ * @param plan The overall plan that triggered the modify operation
113
+ */
114
+ modify(pc: ParameterChange<T>, plan: ModifyPlan<T>): Promise<void>;
115
+ /**
116
+ * Destroy the resource (uninstall) based on the parameters passed in. Only the current parameters will
117
+ * be non-null because in a DESTROY plan, the desired value is null. This method will only be called in
118
+ * stateful mode.
119
+ *
120
+ * Example (Android Studios Resource):
121
+ * 1. We receive a plan of:
122
+ * ```
123
+ * Plan {
124
+ * currentConfig: {
125
+ * name: 'Android Studios.app',
126
+ * directory: '/Application',
127
+ * version: '2022.4'
128
+ * },
129
+ * desiredConfig: null
130
+ * }
131
+ * ```
132
+ * 2. Uninstall version Android Studios 2022.4 and then return.
133
+ *
134
+ * @param plan The plan of what to uninstall. Use only the currentConfig because desiredConfig is null.
135
+ */
136
+ abstract destroy(plan: DestroyPlan<T>): Promise<void>;
137
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A resource represents an object on the system (application, CLI tool, or setting)
3
+ * that has state and can be created and destroyed. Examples of resources include CLI tools
4
+ * like homebrew, docker, and xcode-tools; applications like Google Chrome, Zoom, and OpenVPN;
5
+ * and settings like AWS profiles, git configs and system preference settings.
6
+ */
7
+ export class Resource {
8
+ async initialize() {
9
+ }
10
+ ;
11
+ /**
12
+ * Add custom validation logic in-addition to the default schema validation.
13
+ * In this method throw an error if the object did not validate. The message of the
14
+ * error will be shown to the user.
15
+ * @param parameters
16
+ */
17
+ async validate(parameters) {
18
+ }
19
+ ;
20
+ /**
21
+ * Modify a single parameter of a resource. Modify is optional to override and is only called
22
+ * when a resourceSetting was set to `canModify = true`. This method should only modify
23
+ * a single parameter at a time as specified by the first parameter: ParameterChange.
24
+ *
25
+ * Example (AWS Profile Resource):
26
+ * 1. We receive a parameter change of:
27
+ * ```
28
+ * {
29
+ * name: 'awsAccessKeyId',
30
+ * operation: ParameterOperation.MODIFY,
31
+ * newValue: '123456',
32
+ * previousValue: 'abcdef'
33
+ * }
34
+ * ```
35
+ * 2. Use an if statement to only apply this operation for the parameter `awsAccessKeyId`
36
+ * 3. Update the value of the `aws_access_key_id` to the `newValue` specified in the parameter change
37
+ *
38
+ * @param pc ParameterChange, the parameter name and values to modify on the resource
39
+ * @param plan The overall plan that triggered the modify operation
40
+ */
41
+ async modify(pc, plan) {
42
+ }
43
+ ;
44
+ }
@@ -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 {};