@codifycli/plugin-core 1.0.0-beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -0
- package/.eslintrc.json +30 -0
- package/.github/workflows/release.yaml +19 -0
- package/.github/workflows/unit-test-ci.yaml +18 -0
- package/.prettierrc.json +1 -0
- package/bin/build.js +189 -0
- package/dist/bin/build.d.ts +1 -0
- package/dist/bin/build.js +80 -0
- package/dist/bin/deploy-plugin.d.ts +2 -0
- package/dist/bin/deploy-plugin.js +8 -0
- package/dist/common/errors.d.ts +8 -0
- package/dist/common/errors.js +24 -0
- package/dist/entities/change-set.d.ts +24 -0
- package/dist/entities/change-set.js +152 -0
- package/dist/entities/errors.d.ts +4 -0
- package/dist/entities/errors.js +7 -0
- package/dist/entities/plan-types.d.ts +25 -0
- package/dist/entities/plan-types.js +1 -0
- package/dist/entities/plan.d.ts +15 -0
- package/dist/entities/plan.js +127 -0
- package/dist/entities/plugin.d.ts +16 -0
- package/dist/entities/plugin.js +80 -0
- package/dist/entities/resource-options.d.ts +31 -0
- package/dist/entities/resource-options.js +76 -0
- package/dist/entities/resource-types.d.ts +11 -0
- package/dist/entities/resource-types.js +1 -0
- package/dist/entities/resource.d.ts +42 -0
- package/dist/entities/resource.js +303 -0
- package/dist/entities/stateful-parameter.d.ts +29 -0
- package/dist/entities/stateful-parameter.js +46 -0
- package/dist/entities/transform-parameter.d.ts +4 -0
- package/dist/entities/transform-parameter.js +2 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +26 -0
- package/dist/messages/handlers.d.ts +14 -0
- package/dist/messages/handlers.js +134 -0
- package/dist/messages/sender.d.ts +11 -0
- package/dist/messages/sender.js +57 -0
- package/dist/plan/change-set.d.ts +53 -0
- package/dist/plan/change-set.js +153 -0
- package/dist/plan/plan-types.d.ts +23 -0
- package/dist/plan/plan-types.js +1 -0
- package/dist/plan/plan.d.ts +66 -0
- package/dist/plan/plan.js +328 -0
- package/dist/plugin/plugin.d.ts +24 -0
- package/dist/plugin/plugin.js +200 -0
- package/dist/pty/background-pty.d.ts +21 -0
- package/dist/pty/background-pty.js +127 -0
- package/dist/pty/index.d.ts +50 -0
- package/dist/pty/index.js +20 -0
- package/dist/pty/promise-queue.d.ts +5 -0
- package/dist/pty/promise-queue.js +26 -0
- package/dist/pty/seqeuntial-pty.d.ts +17 -0
- package/dist/pty/seqeuntial-pty.js +119 -0
- package/dist/pty/vitest.config.d.ts +2 -0
- package/dist/pty/vitest.config.js +11 -0
- package/dist/resource/config-parser.d.ts +11 -0
- package/dist/resource/config-parser.js +21 -0
- package/dist/resource/parsed-resource-settings.d.ts +47 -0
- package/dist/resource/parsed-resource-settings.js +196 -0
- package/dist/resource/resource-controller.d.ts +36 -0
- package/dist/resource/resource-controller.js +402 -0
- package/dist/resource/resource-settings.d.ts +303 -0
- package/dist/resource/resource-settings.js +147 -0
- package/dist/resource/resource.d.ts +144 -0
- package/dist/resource/resource.js +44 -0
- package/dist/resource/stateful-parameter.d.ts +165 -0
- package/dist/resource/stateful-parameter.js +94 -0
- package/dist/scripts/deploy.d.ts +1 -0
- package/dist/scripts/deploy.js +2 -0
- package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
- package/dist/stateful-parameter/stateful-parameter-controller.js +81 -0
- package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
- package/dist/stateful-parameter/stateful-parameter.js +43 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +5 -0
- package/dist/utils/codify-spawn.d.ts +29 -0
- package/dist/utils/codify-spawn.js +136 -0
- package/dist/utils/debug.d.ts +2 -0
- package/dist/utils/debug.js +10 -0
- package/dist/utils/file-utils.d.ts +23 -0
- package/dist/utils/file-utils.js +186 -0
- package/dist/utils/functions.d.ts +12 -0
- package/dist/utils/functions.js +74 -0
- package/dist/utils/index.d.ts +46 -0
- package/dist/utils/index.js +271 -0
- package/dist/utils/internal-utils.d.ts +12 -0
- package/dist/utils/internal-utils.js +74 -0
- package/dist/utils/load-resources.d.ts +1 -0
- package/dist/utils/load-resources.js +46 -0
- package/dist/utils/package-json-utils.d.ts +12 -0
- package/dist/utils/package-json-utils.js +34 -0
- package/dist/utils/pty-local-storage.d.ts +2 -0
- package/dist/utils/pty-local-storage.js +2 -0
- package/dist/utils/spawn-2.d.ts +5 -0
- package/dist/utils/spawn-2.js +7 -0
- package/dist/utils/spawn.d.ts +29 -0
- package/dist/utils/spawn.js +124 -0
- package/dist/utils/utils.d.ts +18 -0
- package/dist/utils/utils.js +86 -0
- package/dist/utils/verbosity-level.d.ts +5 -0
- package/dist/utils/verbosity-level.js +9 -0
- package/package.json +59 -0
- package/rollup.config.js +24 -0
- package/src/common/errors.test.ts +43 -0
- package/src/common/errors.ts +31 -0
- package/src/errors.ts +8 -0
- package/src/index.test.ts +6 -0
- package/src/index.ts +30 -0
- package/src/messages/handlers.test.ts +329 -0
- package/src/messages/handlers.ts +181 -0
- package/src/messages/sender.ts +69 -0
- package/src/plan/change-set.test.ts +280 -0
- package/src/plan/change-set.ts +236 -0
- package/src/plan/plan-types.ts +27 -0
- package/src/plan/plan.test.ts +413 -0
- package/src/plan/plan.ts +499 -0
- package/src/plugin/plugin.test.ts +533 -0
- package/src/plugin/plugin.ts +291 -0
- package/src/pty/background-pty.test.ts +69 -0
- package/src/pty/background-pty.ts +154 -0
- package/src/pty/index.test.ts +129 -0
- package/src/pty/index.ts +66 -0
- package/src/pty/promise-queue.ts +33 -0
- package/src/pty/seqeuntial-pty.ts +151 -0
- package/src/pty/sequential-pty.test.ts +194 -0
- package/src/resource/config-parser.ts +42 -0
- package/src/resource/parsed-resource-settings.test.ts +186 -0
- package/src/resource/parsed-resource-settings.ts +307 -0
- package/src/resource/resource-controller-stateful-mode.test.ts +253 -0
- package/src/resource/resource-controller.test.ts +1081 -0
- package/src/resource/resource-controller.ts +563 -0
- package/src/resource/resource-settings.test.ts +1213 -0
- package/src/resource/resource-settings.ts +545 -0
- package/src/resource/resource.ts +157 -0
- package/src/stateful-parameter/stateful-parameter-controller.test.ts +244 -0
- package/src/stateful-parameter/stateful-parameter-controller.ts +111 -0
- package/src/stateful-parameter/stateful-parameter.ts +160 -0
- package/src/utils/debug.ts +11 -0
- package/src/utils/file-utils.test.ts +7 -0
- package/src/utils/file-utils.ts +231 -0
- package/src/utils/functions.ts +103 -0
- package/src/utils/index.ts +340 -0
- package/src/utils/internal-utils.test.ts +52 -0
- package/src/utils/pty-local-storage.ts +3 -0
- package/src/utils/test-utils.test.ts +96 -0
- package/src/utils/verbosity-level.ts +11 -0
- package/tsconfig.json +26 -0
- package/tsconfig.test.json +9 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { JSONSchemaType } from 'ajv';
|
|
2
|
+
import { LinuxDistro, OS, StringIndexedObject } from 'codify-schemas';
|
|
3
|
+
import { ZodObject } from 'zod';
|
|
4
|
+
import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
|
|
5
|
+
import { ParsedResourceSettings } from './parsed-resource-settings.js';
|
|
6
|
+
import { RefreshContext } from './resource.js';
|
|
7
|
+
export interface InputTransformation {
|
|
8
|
+
to: (input: any) => Promise<any> | any;
|
|
9
|
+
from: (current: any, original: any) => Promise<any> | any;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The configuration and settings for a resource.
|
|
13
|
+
*/
|
|
14
|
+
export interface ResourceSettings<T extends StringIndexedObject> {
|
|
15
|
+
/**
|
|
16
|
+
* The typeId of the resource.
|
|
17
|
+
*/
|
|
18
|
+
id: string;
|
|
19
|
+
/**
|
|
20
|
+
* List of supported operating systems
|
|
21
|
+
*/
|
|
22
|
+
operatingSystems: Array<OS>;
|
|
23
|
+
/**
|
|
24
|
+
* List of supported linux distros
|
|
25
|
+
*/
|
|
26
|
+
linuxDistros?: Array<LinuxDistro>;
|
|
27
|
+
/**
|
|
28
|
+
* Schema to validate user configs with. Must be in the format JSON Schema draft07
|
|
29
|
+
*/
|
|
30
|
+
schema?: Partial<JSONSchemaType<T | any>> | ZodObject;
|
|
31
|
+
/**
|
|
32
|
+
* Mark the resource as sensitive. Defaults to false. This prevents the resource from automatically being imported by init and import.
|
|
33
|
+
* This differs from the parameter level sensitivity which also prevents the parameter value from being displayed in the plan.
|
|
34
|
+
*/
|
|
35
|
+
isSensitive?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* An optional description of the resource. This does not affect the behavior of the resource.
|
|
38
|
+
*/
|
|
39
|
+
description?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Allow multiple of the same resource to unique. Set truthy if
|
|
42
|
+
* multiples are allowed, for example for applications, there can be multiple copy of the same application installed
|
|
43
|
+
* on the system. Or there can be multiple git repos. Defaults to false.
|
|
44
|
+
*/
|
|
45
|
+
allowMultiple?: {
|
|
46
|
+
/**
|
|
47
|
+
* A set of parameters that uniquely identifies a resource. The value of these parameters is used to determine which
|
|
48
|
+
* resource is which when multiple can exist at the same time. Defaults to the required parameters inside the json
|
|
49
|
+
* schema.
|
|
50
|
+
*
|
|
51
|
+
* For example:
|
|
52
|
+
* If paramA is required, then if resource1.paramA === resource2.paramA then are the same resource.
|
|
53
|
+
* If resource1.paramA !== resource1.paramA, then they are different.
|
|
54
|
+
*/
|
|
55
|
+
identifyingParameters?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* If multiple copies are allowed then a matcher must be defined to match the desired
|
|
58
|
+
* config with one of the resources currently existing on the system. Return null if there is no match.
|
|
59
|
+
*
|
|
60
|
+
* @param current An array of resources found installed on the system
|
|
61
|
+
* @param desired The desired config to match.
|
|
62
|
+
*
|
|
63
|
+
* @return The matched resource.
|
|
64
|
+
*/
|
|
65
|
+
matcher?: (desired: Partial<T>, current: Partial<T>) => boolean;
|
|
66
|
+
/**
|
|
67
|
+
* This method if supported by the resource returns an array of parameters that represent all of the possible
|
|
68
|
+
* instances of a resource on the system. An example of this is for the git-repository resource, this method returns
|
|
69
|
+
* a list of directories which are git repositories.
|
|
70
|
+
*/
|
|
71
|
+
findAllParameters?: () => Promise<Array<Partial<T>>>;
|
|
72
|
+
} | boolean;
|
|
73
|
+
/**
|
|
74
|
+
* If true, {@link StatefulParameter} remove() will be called before resource destruction. This is useful
|
|
75
|
+
* if the stateful parameter needs to be first uninstalled (cleanup) before the overall resource can be
|
|
76
|
+
* uninstalled. Defaults to false.
|
|
77
|
+
*/
|
|
78
|
+
removeStatefulParametersBeforeDestroy?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* An array of type ids of resources that this resource depends on. This affects the order in which multiple resources are
|
|
81
|
+
* planned and applied.
|
|
82
|
+
*/
|
|
83
|
+
dependencies?: string[];
|
|
84
|
+
/**
|
|
85
|
+
* Options for configuring parameters operations including overriding the equals function, adding default values
|
|
86
|
+
* and applying any input transformations. Use parameter settings to define stateful parameters as well.
|
|
87
|
+
*/
|
|
88
|
+
parameterSettings?: Partial<Record<keyof T, ParameterSetting>>;
|
|
89
|
+
/**
|
|
90
|
+
* A config level transformation that is only applied to the user supplied desired config. This transformation is allowed
|
|
91
|
+
* to add, remove or modify keys as well as values. Changing this transformation for existing libraries will mess up existing states.
|
|
92
|
+
*
|
|
93
|
+
* @param desired
|
|
94
|
+
*/
|
|
95
|
+
transformation?: InputTransformation;
|
|
96
|
+
/**
|
|
97
|
+
* Customize the import and destory behavior of the resource. By default, <code>codify import</code> and <code>codify destroy</code> will call
|
|
98
|
+
* `refresh()` with every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
|
|
99
|
+
* in the schema and will prompt the user for these values before performing the import or destroy.
|
|
100
|
+
*
|
|
101
|
+
* <b>Example:</b><br>
|
|
102
|
+
* Resource `alias` with parameters
|
|
103
|
+
*
|
|
104
|
+
* ```
|
|
105
|
+
* { alias <b>(*required)</b>: string; value: string; }
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* When the user calls `codify import alias`, they will first be prompted to enter the value for `alias`. Refresh
|
|
109
|
+
* is then called with `refresh({ alias: 'user-input', value: null })`. The result returned to the user will then be:
|
|
110
|
+
*
|
|
111
|
+
* ```
|
|
112
|
+
* { type: 'alias', alias: 'user-input', value: 'git push' }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
importAndDestroy?: {
|
|
116
|
+
/**
|
|
117
|
+
* Can this resources be imported? If set to false then the codifyCLI will skip over/not consider this
|
|
118
|
+
* resource valid for imports. Defaults to false.
|
|
119
|
+
*
|
|
120
|
+
* Resources that can't be imported in the core library for example are: action resources
|
|
121
|
+
*/
|
|
122
|
+
preventImport?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Can this resources be destroyed? If set to false then the codifyCLI will skip over/not consider this
|
|
125
|
+
* resource valid for destroys. Defaults to false.
|
|
126
|
+
*/
|
|
127
|
+
preventDestroy?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Customize the required parameters needed to import this resource. By default, the `requiredParameters` are taken
|
|
130
|
+
* from the identifyingParameters for allowMultiple. The `requiredParameters` parameter must be declared if a complex required is declared in
|
|
131
|
+
* the schema (contains `oneOf`, `anyOf`, `allOf`, `if`, `then`, `else`).
|
|
132
|
+
* <br>
|
|
133
|
+
* The user will be prompted for the required parameters before the import starts. This is done because for most resources
|
|
134
|
+
* the required parameters change the behaviour of the refresh (for example for the `alias` resource, the `alias` parmaeter
|
|
135
|
+
* chooses which alias the resource is managing).
|
|
136
|
+
*
|
|
137
|
+
* See {@link importAndDestroy} for more information on how importing works.
|
|
138
|
+
*/
|
|
139
|
+
requiredParameters?: Array<Partial<keyof T>>;
|
|
140
|
+
/**
|
|
141
|
+
* Customize which keys will be refreshed in the import. Typically, `refresh()` statements only refresh
|
|
142
|
+
* the parameters provided as the input. Use `refreshKeys` to control which parameter keys are passed in.
|
|
143
|
+
* <br>
|
|
144
|
+
* By default all parameters (except for {@link requiredParameters }) are passed in with the value `null`. The passed
|
|
145
|
+
* in value can be customized using {@link defaultRefreshValues}
|
|
146
|
+
*
|
|
147
|
+
* See {@link importAndDestroy} for more information on how importing works.
|
|
148
|
+
*/
|
|
149
|
+
refreshKeys?: Array<Partial<keyof T>>;
|
|
150
|
+
/**
|
|
151
|
+
* Customize the value that is passed into refresh when importing. This must only contain keys found in {@link refreshKeys}.
|
|
152
|
+
*
|
|
153
|
+
* See {@link importAndDestroy} for more information on how importing works.
|
|
154
|
+
*/
|
|
155
|
+
defaultRefreshValues?: Partial<T>;
|
|
156
|
+
/**
|
|
157
|
+
* A custom function that maps the input to what gets passed to refresh for imports. If this is set, then refreshKeys and
|
|
158
|
+
* defaultRefreshValues are ignored.
|
|
159
|
+
*
|
|
160
|
+
* @param input
|
|
161
|
+
* @param context
|
|
162
|
+
*/
|
|
163
|
+
refreshMapper?: (input: Partial<T>, context: RefreshContext<T>) => Partial<T>;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The type of parameter. This value is mainly used to determine a pre-set equality method for comparing the current
|
|
168
|
+
* config with desired config. Certain types will have additional options to help support it. For example the type
|
|
169
|
+
* stateful requires a stateful parameter definition and type array takes an isElementEqual method.
|
|
170
|
+
*/
|
|
171
|
+
export type ParameterSettingType = 'any' | 'array' | 'boolean' | 'directory' | 'number' | 'object' | 'setting' | 'stateful' | 'string' | 'version';
|
|
172
|
+
/**
|
|
173
|
+
* Typing information for the parameter setting. This represents a setting on a specific parameter within a
|
|
174
|
+
* resource. Options for configuring parameters operations including overriding the equals function, adding default values
|
|
175
|
+
* and applying any input transformations. See {@link DefaultParameterSetting } for more information.
|
|
176
|
+
* Use parameter settings to define stateful parameters as well.
|
|
177
|
+
*/
|
|
178
|
+
export type ParameterSetting = ArrayParameterSetting | DefaultParameterSetting | StatefulParameterSetting;
|
|
179
|
+
/**
|
|
180
|
+
* The parent class for parameter settings. The options are applicable to array parameter settings
|
|
181
|
+
* as well.
|
|
182
|
+
*/
|
|
183
|
+
export interface DefaultParameterSetting {
|
|
184
|
+
/**
|
|
185
|
+
* The type of the value of this parameter. See {@link ParameterSettingType} for the available options. This value
|
|
186
|
+
* is mainly used to determine the equality method when performing diffing.
|
|
187
|
+
*/
|
|
188
|
+
type?: ParameterSettingType;
|
|
189
|
+
/**
|
|
190
|
+
* Mark the field as sensitive. Defaults to false. This has two side effects:
|
|
191
|
+
* 1. When displaying this field in the plan, it will be replaced with asterisks
|
|
192
|
+
* 2. When importing, resources with sensitive fields will be skipped unless the user explicitly allows it.
|
|
193
|
+
*/
|
|
194
|
+
isSensitive?: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Default value for the parameter. If a value is not provided in the config, then this value will be used.
|
|
197
|
+
*/
|
|
198
|
+
default?: unknown;
|
|
199
|
+
/**
|
|
200
|
+
* A transformation of the input value for this parameter. Two transformations need to be provided: to (from desired to
|
|
201
|
+
* the internal type), and from (from the internal type back to desired). All transformations need to be bi-directional
|
|
202
|
+
* to support imports properly
|
|
203
|
+
*
|
|
204
|
+
* @param input The original parameter value from the desired config.
|
|
205
|
+
*/
|
|
206
|
+
transformation?: InputTransformation;
|
|
207
|
+
/**
|
|
208
|
+
* Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
|
|
209
|
+
* This value will override the pre-set equality function from the type. Return true if the desired value is
|
|
210
|
+
* equivalent to the current value.
|
|
211
|
+
*
|
|
212
|
+
* @param desired The desired value.
|
|
213
|
+
* @param current The current value.
|
|
214
|
+
*
|
|
215
|
+
* @return Return true if equal
|
|
216
|
+
*/
|
|
217
|
+
isEqual?: ((desired: any, current: any) => boolean) | ParameterSettingType;
|
|
218
|
+
/**
|
|
219
|
+
* Chose if the resource can be modified instead of re-created when there is a change to this parameter.
|
|
220
|
+
* Defaults to false (re-create).
|
|
221
|
+
*
|
|
222
|
+
* Examples:
|
|
223
|
+
* 1. Settings like git user name and git user email that have setter calls and don't require the re-installation of git
|
|
224
|
+
* 2. AWS profile secret keys that can be updated without the re-installation of AWS CLI
|
|
225
|
+
*/
|
|
226
|
+
canModify?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* This option allows the plan to skip this parameter entirely as it is used for setting purposes only. The value
|
|
229
|
+
* of this parameter is used to configure the resource or other parameters.
|
|
230
|
+
*
|
|
231
|
+
* Examples:
|
|
232
|
+
* 1. homebrew.onlyPlanUserInstalled option will tell homebrew to filter by --installed-on-request. But the value,
|
|
233
|
+
* of the parameter itself (true or false) does not have an impact on the plan
|
|
234
|
+
*/
|
|
235
|
+
setting?: boolean;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Array type specific settings. See {@link DefaultParameterSetting } for a full list of options.
|
|
239
|
+
*/
|
|
240
|
+
export interface ArrayParameterSetting extends DefaultParameterSetting {
|
|
241
|
+
type: 'array';
|
|
242
|
+
/**
|
|
243
|
+
* An element level equality function for arrays. The diffing algorithm will take isElementEqual and use it in a
|
|
244
|
+
* O(n^2) equality comparison to determine if the overall array is equal. This value will override the pre-set equality
|
|
245
|
+
* function for arrays (desired === current). Return true if the desired element is equivalent to the current element.
|
|
246
|
+
*
|
|
247
|
+
* @param desired An element of the desired array
|
|
248
|
+
* @param current An element of the current array
|
|
249
|
+
*
|
|
250
|
+
* @return Return true if desired is equivalent to current.
|
|
251
|
+
*/
|
|
252
|
+
isElementEqual?: ((desired: any, current: any) => boolean) | ParameterSettingType;
|
|
253
|
+
/**
|
|
254
|
+
* Filter the contents of the refreshed array by the desired. This way items currently on the system but not
|
|
255
|
+
* in desired don't show up in the plan.
|
|
256
|
+
*
|
|
257
|
+
* <b>For example, for the nvm resource:</b>
|
|
258
|
+
* <ul>
|
|
259
|
+
* <li>Desired (20.18.0, 18.9.0, 16.3.1)</li>
|
|
260
|
+
* <li>Current (20.18.0, 22.1.3, 12.1.0)</li>
|
|
261
|
+
* </ul>
|
|
262
|
+
*
|
|
263
|
+
* Without filtering the plan will be:
|
|
264
|
+
* (~20.18.0, +18.9.0, +16.3.1, -22.1.3, -12.1.0)<br>
|
|
265
|
+
* With filtering the plan is: (~20.18.0, +18.9.0, +16.3.1)
|
|
266
|
+
*
|
|
267
|
+
* As you can see, filtering prevents items currently installed on the system from being removed.
|
|
268
|
+
*
|
|
269
|
+
* Defaults to true.
|
|
270
|
+
*/
|
|
271
|
+
filterInStatelessMode?: ((desired: any[], current: any[]) => any[]) | boolean;
|
|
272
|
+
/**
|
|
273
|
+
* The type of the array item. See {@link ParameterSettingType} for the available options. This value
|
|
274
|
+
* is mainly used to determine the equality method when performing diffing.
|
|
275
|
+
*/
|
|
276
|
+
itemType?: ParameterSettingType;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Stateful parameter type specific settings. A stateful parameter is a sub-resource that can hold its own
|
|
280
|
+
* state but is still tied to the overall state of the resource. For example 'homebrew' is represented
|
|
281
|
+
* as a resource and taps, formulas and casks are represented as a stateful parameter. A formula can be installed,
|
|
282
|
+
* modified and removed (has state) but it is still tied to the overall lifecycle of homebrew.
|
|
283
|
+
*
|
|
284
|
+
*/
|
|
285
|
+
export interface StatefulParameterSetting extends DefaultParameterSetting {
|
|
286
|
+
type: 'stateful';
|
|
287
|
+
/**
|
|
288
|
+
* The stateful parameter definition. A stateful parameter is a sub-resource that can hold its own
|
|
289
|
+
* state but is still tied to the overall state of the resource. For example 'homebrew' is represented
|
|
290
|
+
* as a resource and taps, formulas and casks are represented as a stateful parameter. A formula can be installed,
|
|
291
|
+
* modified and removed (has state) but it is still tied to the overall lifecycle of homebrew.
|
|
292
|
+
*/
|
|
293
|
+
definition: ArrayStatefulParameter<any, unknown> | StatefulParameter<any, unknown>;
|
|
294
|
+
/**
|
|
295
|
+
* The order multiple stateful parameters should be applied in. The order is applied in ascending order (1, 2, 3...).
|
|
296
|
+
*/
|
|
297
|
+
order?: number;
|
|
298
|
+
}
|
|
299
|
+
export declare function resolveEqualsFn(parameter: ParameterSetting): (desired: unknown, current: unknown) => boolean;
|
|
300
|
+
export declare function resolveElementEqualsFn(parameter: ArrayParameterSetting): (desired: unknown, current: unknown) => boolean;
|
|
301
|
+
export declare function resolveFnFromEqualsFnOrString(fnOrString: ((a: unknown, b: unknown) => boolean) | ParameterSettingType | undefined): ((a: unknown, b: unknown) => boolean) | undefined;
|
|
302
|
+
export declare function resolveParameterTransformFn(parameter: ParameterSetting): InputTransformation | undefined;
|
|
303
|
+
export declare function resolveMatcher<T extends StringIndexedObject>(settings: ParsedResourceSettings<T>): (desired: Partial<T>, current: Partial<T>) => boolean;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import isObjectsEqual from 'lodash.isequal';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { addVariablesToPath, areArraysEqual, resolvePathWithVariables, tildify, untildify } from '../utils/functions.js';
|
|
4
|
+
const ParameterEqualsDefaults = {
|
|
5
|
+
'boolean': (a, b) => Boolean(a) === Boolean(b),
|
|
6
|
+
'directory'(a, b) {
|
|
7
|
+
let transformedA = resolvePathWithVariables(untildify(String(a)));
|
|
8
|
+
let transformedB = resolvePathWithVariables(untildify(String(b)));
|
|
9
|
+
if (transformedA.startsWith('.')) { // Only relative paths start with '.'
|
|
10
|
+
transformedA = path.resolve(transformedA);
|
|
11
|
+
}
|
|
12
|
+
if (transformedB.startsWith('.')) { // Only relative paths start with '.'
|
|
13
|
+
transformedB = path.resolve(transformedB);
|
|
14
|
+
}
|
|
15
|
+
// macOS has case-insensitive filesystem by default, Linux is case-sensitive
|
|
16
|
+
const isCaseSensitive = process.platform === 'linux';
|
|
17
|
+
if (!isCaseSensitive) {
|
|
18
|
+
transformedA = transformedA.toLowerCase();
|
|
19
|
+
transformedB = transformedB.toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
return transformedA === transformedB;
|
|
22
|
+
},
|
|
23
|
+
'number': (a, b) => Number(a) === Number(b),
|
|
24
|
+
'string': (a, b) => String(a) === String(b),
|
|
25
|
+
'version': (desired, current) => String(current).includes(String(desired)),
|
|
26
|
+
'object': isObjectsEqual,
|
|
27
|
+
};
|
|
28
|
+
export function resolveEqualsFn(parameter) {
|
|
29
|
+
// Setting parameters do not impact the plan
|
|
30
|
+
if (parameter.setting) {
|
|
31
|
+
return () => true;
|
|
32
|
+
}
|
|
33
|
+
const isEqual = resolveFnFromEqualsFnOrString(parameter.isEqual);
|
|
34
|
+
if (parameter.type === 'array') {
|
|
35
|
+
return isEqual ?? areArraysEqual.bind(areArraysEqual, resolveElementEqualsFn(parameter));
|
|
36
|
+
}
|
|
37
|
+
if (parameter.type === 'stateful') {
|
|
38
|
+
return resolveEqualsFn(parameter.definition.getSettings());
|
|
39
|
+
}
|
|
40
|
+
return isEqual ?? ParameterEqualsDefaults[parameter.type] ?? (((a, b) => a === b));
|
|
41
|
+
}
|
|
42
|
+
export function resolveElementEqualsFn(parameter) {
|
|
43
|
+
if (parameter.isElementEqual) {
|
|
44
|
+
const elementEq = resolveFnFromEqualsFnOrString(parameter.isElementEqual);
|
|
45
|
+
if (elementEq) {
|
|
46
|
+
return elementEq;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (parameter.itemType && ParameterEqualsDefaults[parameter.itemType]) {
|
|
50
|
+
return ParameterEqualsDefaults[parameter.itemType];
|
|
51
|
+
}
|
|
52
|
+
return (a, b) => a === b;
|
|
53
|
+
}
|
|
54
|
+
// This resolves the fn if it is a string.
|
|
55
|
+
// A string can be specified to use a default equals method
|
|
56
|
+
export function resolveFnFromEqualsFnOrString(fnOrString) {
|
|
57
|
+
if (fnOrString && typeof fnOrString === 'string') {
|
|
58
|
+
if (!ParameterEqualsDefaults[fnOrString]) {
|
|
59
|
+
throw new Error(`isEqual of type ${fnOrString} was not found`);
|
|
60
|
+
}
|
|
61
|
+
return ParameterEqualsDefaults[fnOrString];
|
|
62
|
+
}
|
|
63
|
+
return fnOrString;
|
|
64
|
+
}
|
|
65
|
+
const ParameterTransformationDefaults = {
|
|
66
|
+
'directory': {
|
|
67
|
+
to: (a) => resolvePathWithVariables((untildify(String(a)))),
|
|
68
|
+
from(a, original) {
|
|
69
|
+
if (ParameterEqualsDefaults.directory(a, original)) {
|
|
70
|
+
return original;
|
|
71
|
+
}
|
|
72
|
+
return tildify(addVariablesToPath(String(a)));
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
'string': {
|
|
76
|
+
to: String,
|
|
77
|
+
from: String,
|
|
78
|
+
},
|
|
79
|
+
'boolean': {
|
|
80
|
+
to: Boolean,
|
|
81
|
+
from: Boolean,
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
export function resolveParameterTransformFn(parameter) {
|
|
85
|
+
if (parameter.type === 'stateful' && !parameter.transformation) {
|
|
86
|
+
const sp = parameter.definition.getSettings();
|
|
87
|
+
if (sp.transformation) {
|
|
88
|
+
return parameter.definition?.getSettings()?.transformation;
|
|
89
|
+
}
|
|
90
|
+
return sp.type ? ParameterTransformationDefaults[sp.type] : undefined;
|
|
91
|
+
}
|
|
92
|
+
if (parameter.type === 'array'
|
|
93
|
+
&& parameter.itemType
|
|
94
|
+
&& ParameterTransformationDefaults[parameter.itemType]
|
|
95
|
+
&& !parameter.transformation) {
|
|
96
|
+
const itemType = parameter.itemType;
|
|
97
|
+
const itemTransformation = ParameterTransformationDefaults[itemType];
|
|
98
|
+
return {
|
|
99
|
+
to(input) {
|
|
100
|
+
return input.map((i) => itemTransformation.to(i));
|
|
101
|
+
},
|
|
102
|
+
from(input, original) {
|
|
103
|
+
return input.map((i, idx) => {
|
|
104
|
+
const originalElement = Array.isArray(original)
|
|
105
|
+
? original.find((o) => resolveElementEqualsFn(parameter)(o, i)) ?? original[idx]
|
|
106
|
+
: original;
|
|
107
|
+
return itemTransformation.from(i, originalElement);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return parameter.transformation ?? ParameterTransformationDefaults[parameter.type] ?? undefined;
|
|
113
|
+
}
|
|
114
|
+
export function resolveMatcher(settings) {
|
|
115
|
+
return typeof settings.allowMultiple === 'boolean' || !settings.allowMultiple?.matcher
|
|
116
|
+
? ((desired, current) => {
|
|
117
|
+
if (!desired || !current) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (!settings.allowMultiple) {
|
|
121
|
+
throw new Error(`Matching only works when allow multiple is enabled. Type: ${settings.id}`);
|
|
122
|
+
}
|
|
123
|
+
const requiredParameters = typeof settings.allowMultiple === 'object'
|
|
124
|
+
? settings.allowMultiple?.identifyingParameters ?? settings.schema?.required ?? []
|
|
125
|
+
: settings.schema?.required ?? [];
|
|
126
|
+
return requiredParameters.every((key) => {
|
|
127
|
+
const currentParameter = current[key];
|
|
128
|
+
const desiredParameter = desired[key];
|
|
129
|
+
// If both desired and current don't have a certain parameter then we assume they are the same
|
|
130
|
+
if (!currentParameter && !desiredParameter) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if (!currentParameter) {
|
|
134
|
+
console.warn(`Unable to find required parameter for current ${currentParameter}`);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (!desiredParameter) {
|
|
138
|
+
console.warn(`Unable to find required parameter for current ${currentParameter}`);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
const parameterSetting = settings.parameterSettings?.[key];
|
|
142
|
+
const isEq = parameterSetting ? resolveEqualsFn(parameterSetting) : null;
|
|
143
|
+
return isEq?.(desiredParameter, currentParameter) ?? currentParameter === desiredParameter;
|
|
144
|
+
});
|
|
145
|
+
})
|
|
146
|
+
: settings.allowMultiple.matcher;
|
|
147
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
export interface RefreshContext<T extends StringIndexedObject> {
|
|
6
|
+
isStateful: boolean;
|
|
7
|
+
commandType: 'destroy' | 'import' | 'plan' | 'validationPlan';
|
|
8
|
+
originalDesiredConfig: Partial<T> | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A resource represents an object on the system (application, CLI tool, or setting)
|
|
12
|
+
* that has state and can be created and destroyed. Examples of resources include CLI tools
|
|
13
|
+
* like homebrew, docker, and xcode-tools; applications like Google Chrome, Zoom, and OpenVPN;
|
|
14
|
+
* and settings like AWS profiles, git configs and system preference settings.
|
|
15
|
+
*/
|
|
16
|
+
export declare abstract class Resource<T extends StringIndexedObject> {
|
|
17
|
+
/**
|
|
18
|
+
* Return the settings for the resource. Consult the typing for {@link ResourceSettings} for
|
|
19
|
+
* a description of the options.
|
|
20
|
+
*
|
|
21
|
+
* **Parameters**:
|
|
22
|
+
* - id: The id of the resource. This translates to the `type` id parameter in codify.json configs
|
|
23
|
+
* - schema: A JSON schema used to validate user input
|
|
24
|
+
* - allowMultiple: Allow multiple copies of the resource to exist at the same time. If true then,
|
|
25
|
+
* a matcher must be defined that matches a user defined config and a single resource on the system.
|
|
26
|
+
* - removeStatefulParametersBeforeDestory: Call the delete methods of stateful parameters before destorying
|
|
27
|
+
* the base resource. Defaults to false.
|
|
28
|
+
* - dependencies: Specify the ids of any resources that this resource depends on
|
|
29
|
+
* - parameterSettings: Parameter specific settings. Use this to define custom equals functions, default values
|
|
30
|
+
* and input transformations
|
|
31
|
+
* - inputTransformation: Transform the input value.
|
|
32
|
+
*
|
|
33
|
+
* @return ResourceSettings The resource settings
|
|
34
|
+
*/
|
|
35
|
+
abstract getSettings(): ResourceSettings<T>;
|
|
36
|
+
initialize(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Add custom validation logic in-addition to the default schema validation.
|
|
39
|
+
* In this method throw an error if the object did not validate. The message of the
|
|
40
|
+
* error will be shown to the user.
|
|
41
|
+
* @param parameters
|
|
42
|
+
*/
|
|
43
|
+
validate(parameters: Partial<T>): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Return the status of the resource on the system. If multiple resources exist, then return all instances of
|
|
46
|
+
* the resource back. Query for the individual parameters specified in the parameter param.
|
|
47
|
+
* Return null if the resource does not exist.
|
|
48
|
+
*
|
|
49
|
+
* Example (Android Studios Resource):
|
|
50
|
+
* 1. Receive Input:
|
|
51
|
+
* ```
|
|
52
|
+
* {
|
|
53
|
+
* name: 'Android Studios.app'
|
|
54
|
+
* directory: '/Application',
|
|
55
|
+
* version: '2023.2'
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
* 2. Query the system for any installed Android studio versions.
|
|
59
|
+
* 3. In this example we find that there is an 2023.2 version installed and an
|
|
60
|
+
* additional 2024.3-beta version installed as well.
|
|
61
|
+
* 4. We would return:
|
|
62
|
+
* ```
|
|
63
|
+
* [
|
|
64
|
+
* { name: 'Android Studios.app', directory: '/Application', version: '2023.2' },
|
|
65
|
+
* { name: 'Android Studios Preview.app', directory: '/Application', version: '2024.3' },
|
|
66
|
+
* ]
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @param parameters The parameters to refresh. In stateless mode this will be the parameters
|
|
70
|
+
* of the desired config. In stateful mode, this will be parameters of the state config + the desired
|
|
71
|
+
* config of any new parameters.
|
|
72
|
+
*
|
|
73
|
+
* @param context Context surrounding the request
|
|
74
|
+
*
|
|
75
|
+
* @return A config or an array of configs representing the status of the resource on the
|
|
76
|
+
* system currently
|
|
77
|
+
*/
|
|
78
|
+
abstract refresh(parameters: Partial<T>, context: RefreshContext<T>): Promise<Array<Partial<T>> | Partial<T> | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Create the resource (install) based on the parameters passed in. Only the desired parameters will
|
|
81
|
+
* be non-null because in a CREATE plan, the current value is null.
|
|
82
|
+
*
|
|
83
|
+
* Example (Android Studios Resource):
|
|
84
|
+
* 1. We receive a plan of:
|
|
85
|
+
* ```
|
|
86
|
+
* Plan {
|
|
87
|
+
* desiredConfig: {
|
|
88
|
+
* name: 'Android Studios.app',
|
|
89
|
+
* directory: '/Application',
|
|
90
|
+
* version: '2023.2'
|
|
91
|
+
* }
|
|
92
|
+
* currentConfig: null,
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
* 2. Install version Android Studios 2023.2 and then return.
|
|
96
|
+
*
|
|
97
|
+
* @param plan The plan of what to install. Use only the desiredConfig because currentConfig is null.
|
|
98
|
+
*/
|
|
99
|
+
abstract create(plan: CreatePlan<T>): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Modify a single parameter of a resource. Modify is optional to override and is only called
|
|
102
|
+
* when a resourceSetting was set to `canModify = true`. This method should only modify
|
|
103
|
+
* a single parameter at a time as specified by the first parameter: ParameterChange.
|
|
104
|
+
*
|
|
105
|
+
* Example (AWS Profile Resource):
|
|
106
|
+
* 1. We receive a parameter change of:
|
|
107
|
+
* ```
|
|
108
|
+
* {
|
|
109
|
+
* name: 'awsAccessKeyId',
|
|
110
|
+
* operation: ParameterOperation.MODIFY,
|
|
111
|
+
* newValue: '123456',
|
|
112
|
+
* previousValue: 'abcdef'
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
* 2. Use an if statement to only apply this operation for the parameter `awsAccessKeyId`
|
|
116
|
+
* 3. Update the value of the `aws_access_key_id` to the `newValue` specified in the parameter change
|
|
117
|
+
*
|
|
118
|
+
* @param pc ParameterChange, the parameter name and values to modify on the resource
|
|
119
|
+
* @param plan The overall plan that triggered the modify operation
|
|
120
|
+
*/
|
|
121
|
+
modify(pc: ParameterChange<T>, plan: ModifyPlan<T>): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Destroy the resource (uninstall) based on the parameters passed in. Only the current parameters will
|
|
124
|
+
* be non-null because in a DESTROY plan, the desired value is null. This method will only be called in
|
|
125
|
+
* stateful mode.
|
|
126
|
+
*
|
|
127
|
+
* Example (Android Studios Resource):
|
|
128
|
+
* 1. We receive a plan of:
|
|
129
|
+
* ```
|
|
130
|
+
* Plan {
|
|
131
|
+
* currentConfig: {
|
|
132
|
+
* name: 'Android Studios.app',
|
|
133
|
+
* directory: '/Application',
|
|
134
|
+
* version: '2022.4'
|
|
135
|
+
* },
|
|
136
|
+
* desiredConfig: null
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
* 2. Uninstall version Android Studios 2022.4 and then return.
|
|
140
|
+
*
|
|
141
|
+
* @param plan The plan of what to uninstall. Use only the currentConfig because desiredConfig is null.
|
|
142
|
+
*/
|
|
143
|
+
abstract destroy(plan: DestroyPlan<T>): Promise<void>;
|
|
144
|
+
}
|
|
@@ -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
|
+
}
|