codify-plugin-lib 1.0.98 → 1.0.100

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/dist/plan/plan.js CHANGED
@@ -126,7 +126,7 @@ export class Plan {
126
126
  }
127
127
  // TODO: Add object handling here in addition to arrays in the future
128
128
  const arrayStatefulParameters = Object.fromEntries(Object.entries(filteredCurrent)
129
- .filter(([k, v]) => isArrayStatefulParameter(k, v))
129
+ .filter(([k, v]) => isArrayParameterWithFiltering(k, v))
130
130
  .map(([k, v]) => [k, filterArrayStatefulParameter(k, v)]));
131
131
  return { ...filteredCurrent, ...arrayStatefulParameters };
132
132
  function filterCurrent() {
@@ -143,18 +143,23 @@ export class Plan {
143
143
  return Object.fromEntries(Object.entries(current)
144
144
  .filter(([k]) => keys.has(k)));
145
145
  }
146
- function isArrayStatefulParameter(k, v) {
147
- return settings.parameterSettings?.[k]?.type === 'stateful'
148
- && settings.parameterSettings[k].definition.getSettings().type === 'array'
146
+ function isArrayParameterWithFiltering(k, v) {
147
+ return (((settings.parameterSettings?.[k]?.type === 'stateful'
148
+ && settings.parameterSettings[k].definition.getSettings().type === 'array')
149
+ && (settings.parameterSettings[k].definition.getSettings().filterInStatelessMode ?? true)) || (settings.parameterSettings?.[k]?.type === 'array'
150
+ && ((settings.parameterSettings?.[k]).filterInStatelessMode ?? true)))
149
151
  && Array.isArray(v);
150
152
  }
151
153
  // For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
152
154
  function filterArrayStatefulParameter(k, v) {
153
155
  const desiredArray = desired[k];
154
- const matcher = settings.parameterSettings[k]
155
- .definition
156
- .getSettings()
157
- .isElementEqual ?? ((a, b) => a === b);
156
+ const matcher = settings.parameterSettings[k].type === 'stateful'
157
+ ? settings.parameterSettings[k]
158
+ .definition
159
+ .getSettings()
160
+ .isElementEqual ?? ((a, b) => a === b)
161
+ : settings.parameterSettings[k]
162
+ .isElementEqual ?? ((a, b) => a === b);
158
163
  const desiredCopy = [...desiredArray];
159
164
  const currentCopy = [...v];
160
165
  const result = [];
@@ -115,10 +115,26 @@ export class ParsedResourceSettings {
115
115
  'determine the prompt to ask users during imports. It can\'t parse which parameters are needed when ' +
116
116
  'required is declared conditionally.');
117
117
  }
118
- const importRequiredParameterNotInSchema = this.settings.import?.requiredParameters?.filter((p) => !(schema?.properties[p]));
119
- if (schema && importRequiredParameterNotInSchema && importRequiredParameterNotInSchema.length > 0) {
120
- throw new Error(`The following properties were declared in settings.import.requiredParameters but were not found in the schema:
121
- ${JSON.stringify(importRequiredParameterNotInSchema, null, 2)}`);
118
+ if (this.settings.import) {
119
+ const { requiredParameters, refreshKeys, defaultRefreshValues } = this.settings.import;
120
+ const requiredParametersNotInSchema = requiredParameters
121
+ ?.filter((p) => schema && !(schema.properties[p]));
122
+ if (schema && requiredParametersNotInSchema && requiredParametersNotInSchema.length > 0) {
123
+ throw new Error(`The following properties were declared in settings.import.requiredParameters but were not found in the schema:
124
+ ${JSON.stringify(requiredParametersNotInSchema, null, 2)}`);
125
+ }
126
+ const refreshKeyNotInSchema = refreshKeys
127
+ ?.filter((k) => schema && !(schema.properties[k]));
128
+ if (schema && refreshKeyNotInSchema && refreshKeyNotInSchema.length > 0) {
129
+ throw new Error(`The following properties were declared in settings.import.refreshKeys but were not found in the schema:
130
+ ${JSON.stringify(requiredParametersNotInSchema, null, 2)}`);
131
+ }
132
+ const refreshValueNotInRefreshKey = Object.entries(defaultRefreshValues ?? {})
133
+ .filter(([k]) => schema && !(schema.properties[k])).map(([k]) => k);
134
+ if (schema && refreshValueNotInRefreshKey.length > 0) {
135
+ throw new Error(`Properties declared in defaultRefreshValues must already be declared in refreshKeys:
136
+ ${JSON.stringify(refreshValueNotInRefreshKey, null, 2)}`);
137
+ }
122
138
  }
123
139
  }
124
140
  validateParameterEqualsFn(parameter, key) {
@@ -134,13 +134,15 @@ export class ResourceController {
134
134
  this.addDefaultValues(config);
135
135
  await this.applyTransformParameters(config);
136
136
  // Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
137
- const parametersToRefresh = this.settings.import?.refreshParameters
137
+ const parametersToRefresh = this.settings.import?.refreshKeys
138
138
  ? {
139
- ...Object.fromEntries(this.settings.import?.refreshParameters.map((k) => [k, null])),
139
+ ...Object.fromEntries(this.settings.import?.refreshKeys.map((k) => [k, null])),
140
+ ...this.settings.import?.defaultRefreshValues,
140
141
  ...config,
141
142
  }
142
143
  : {
143
144
  ...Object.fromEntries(this.getAllParameterKeys().map((k) => [k, null])),
145
+ ...this.settings.import?.defaultRefreshValues,
144
146
  ...config,
145
147
  };
146
148
  // Parse data from the user supplied config
@@ -52,9 +52,54 @@ export interface ResourceSettings<T extends StringIndexedObject> {
52
52
  * @param desired
53
53
  */
54
54
  inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
55
+ /**
56
+ * Customize the import behavior of the resource. By default, <code>codify import</code> will call `refresh()` with
57
+ * every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
58
+ * in the schema and will prompt the user for these values before performing the import.
59
+ *
60
+ * <b>Example:</b><br>
61
+ * Resource `alias` with parameters
62
+ *
63
+ * ```
64
+ * { alias <b>(*required)</b>: string; value: string; }
65
+ * ```
66
+ *
67
+ * When the user calls `codify import alias`, they will first be prompted to enter the value for `alias`. Refresh
68
+ * is then called with `refresh({ alias: 'user-input', value: null })`. The result returned to the user will then be:
69
+ *
70
+ * ```
71
+ * { type: 'alias', alias: 'user-input', value: 'git push' }
72
+ * ```
73
+ */
55
74
  import?: {
75
+ /**
76
+ * Customize the required parameters needed to import this resource. By default, the `requiredParameters` are taken
77
+ * from the JSON schema. The `requiredParameters` parameter must be declared if a complex required is declared in
78
+ * the schema (contains `oneOf`, `anyOf`, `allOf`, `if`, `then`, `else`).
79
+ * <br>
80
+ * The user will be prompted for the required parameters before the import starts. This is done because for most resources
81
+ * the required parameters change the behaviour of the refresh (for example for the `alias` resource, the `alias` parmaeter
82
+ * chooses which alias the resource is managing).
83
+ *
84
+ * See {@link import} for more information on how importing works.
85
+ */
56
86
  requiredParameters?: Array<Partial<keyof T>>;
57
- refreshParameters?: Array<Partial<keyof T>>;
87
+ /**
88
+ * Customize which keys will be refreshed in the import. Typically, `refresh()` statements only refresh
89
+ * the parameters provided as the input. Use `refreshKeys` to control which parameter keys are passed in.
90
+ * <br>
91
+ * By default all parameters (except for {@link requiredParameters }) are passed in with the value `null`. The passed
92
+ * in value can be customized using {@link defaultRefreshValues}
93
+ *
94
+ * See {@link import} for more information on how importing works.
95
+ */
96
+ refreshKeys?: Array<Partial<keyof T>>;
97
+ /**
98
+ * Customize the value that is passed into refresh when importing. This must only contain keys found in {@link refreshKeys}.
99
+ *
100
+ * See {@link import} for more information on how importing works.
101
+ */
102
+ defaultRefreshValues?: Partial<T>;
58
103
  };
59
104
  }
60
105
  /**
@@ -128,6 +173,25 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
128
173
  * @return Return true if desired is equivalent to current.
129
174
  */
130
175
  isElementEqual?: (desired: any, current: any) => boolean;
176
+ /**
177
+ * Filter the contents of the refreshed array by the desired. This way items currently on the system but not
178
+ * in desired don't show up in the plan.
179
+ *
180
+ * <b>For example, for the nvm resource:</b>
181
+ * <ul>
182
+ * <li>Desired (20.18.0, 18.9.0, 16.3.1)</li>
183
+ * <li>Current (20.18.0, 22.1.3, 12.1.0)</li>
184
+ * </ul>
185
+ *
186
+ * Without filtering the plan will be:
187
+ * (~20.18.0, +18.9.0, +16.3.1, -22.1.3, -12.1.0)<br>
188
+ * With filtering the plan is: (~20.18.0, +18.9.0, +16.3.1)
189
+ *
190
+ * As you can see, filtering prevents items currently installed on the system from being removed.
191
+ *
192
+ * Defaults to true.
193
+ */
194
+ filterInStatelessMode?: boolean;
131
195
  }
132
196
  /**
133
197
  * Stateful parameter type specific settings. A stateful parameter is a sub-resource that can hold its own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codify-plugin-lib",
3
- "version": "1.0.98",
3
+ "version": "1.0.100",
4
4
  "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -147,6 +147,79 @@ describe('Plan entity tests', () => {
147
147
  operation: ResourceOperation.RECREATE
148
148
  })
149
149
  })
150
+
151
+ it('Filters array parameters in stateless mode (by default)', async () => {
152
+ const resource = new class extends TestResource {
153
+ getSettings(): ResourceSettings<any> {
154
+ return {
155
+ id: 'type',
156
+ parameterSettings: {
157
+ propZ: { type: 'array', isElementEqual: (a, b) => b.includes(a) }
158
+ }
159
+ }
160
+ }
161
+
162
+ async refresh(): Promise<Partial<any> | null> {
163
+ return {
164
+ propZ: [
165
+ '20.15.0',
166
+ '20.15.1'
167
+ ]
168
+ }
169
+ }
170
+ }
171
+
172
+ const controller = new ResourceController(resource);
173
+ const plan = await controller.plan({
174
+ propZ: ['20.15'],
175
+ } as any)
176
+
177
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
178
+ })
179
+
180
+ it('Doesn\'t filters array parameters if filtering is disabled', async () => {
181
+ const resource = new class extends TestResource {
182
+ getSettings(): ResourceSettings<any> {
183
+ return {
184
+ id: 'type',
185
+ parameterSettings: {
186
+ propZ: { type: 'array', canModify: true, isElementEqual: (a, b) => b.includes(a), filterInStatelessMode: false }
187
+ }
188
+ }
189
+ }
190
+
191
+ async refresh(): Promise<Partial<any> | null> {
192
+ return {
193
+ propZ: [
194
+ '20.15.0',
195
+ '20.15.1'
196
+ ]
197
+ }
198
+ }
199
+ }
200
+
201
+ const controller = new ResourceController(resource);
202
+ const plan = await controller.plan({
203
+ propZ: ['20.15'],
204
+ } as any)
205
+
206
+ expect(plan.changeSet).toMatchObject({
207
+ operation: ResourceOperation.MODIFY,
208
+ parameterChanges: expect.arrayContaining([
209
+ expect.objectContaining({
210
+ name: 'propZ',
211
+ previousValue: expect.arrayContaining([
212
+ '20.15.0',
213
+ '20.15.1'
214
+ ]),
215
+ newValue: expect.arrayContaining([
216
+ '20.15'
217
+ ]),
218
+ operation: 'modify'
219
+ })
220
+ ])
221
+ })
222
+ })
150
223
  })
151
224
 
152
225
  function createTestResource() {
package/src/plan/plan.ts CHANGED
@@ -220,7 +220,7 @@ export class Plan<T extends StringIndexedObject> {
220
220
  // TODO: Add object handling here in addition to arrays in the future
221
221
  const arrayStatefulParameters = Object.fromEntries(
222
222
  Object.entries(filteredCurrent)
223
- .filter(([k, v]) => isArrayStatefulParameter(k, v))
223
+ .filter(([k, v]) => isArrayParameterWithFiltering(k, v))
224
224
  .map(([k, v]) => [k, filterArrayStatefulParameter(k, v)])
225
225
  )
226
226
 
@@ -247,19 +247,27 @@ export class Plan<T extends StringIndexedObject> {
247
247
  ) as Partial<T>;
248
248
  }
249
249
 
250
- function isArrayStatefulParameter(k: string, v: T[keyof T]): boolean {
251
- return settings.parameterSettings?.[k]?.type === 'stateful'
252
- && (settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings().type === 'array'
250
+ function isArrayParameterWithFiltering(k: string, v: T[keyof T]): boolean {
251
+ return (((settings.parameterSettings?.[k]?.type === 'stateful'
252
+ && (settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings().type === 'array')
253
+ && (((settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings() as ArrayParameterSetting).filterInStatelessMode ?? true)
254
+ ) || (
255
+ settings.parameterSettings?.[k]?.type === 'array'
256
+ && ((settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode ?? true)
257
+ ))
253
258
  && Array.isArray(v)
254
259
  }
255
260
 
256
261
  // For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
257
262
  function filterArrayStatefulParameter(k: string, v: unknown[]): unknown[] {
258
263
  const desiredArray = desired![k] as unknown[];
259
- const matcher = ((settings.parameterSettings![k] as StatefulParameterSetting)
260
- .definition
261
- .getSettings() as ArrayParameterSetting)
262
- .isElementEqual ?? ((a, b) => a === b);
264
+ const matcher = settings.parameterSettings![k]!.type === 'stateful'
265
+ ? ((settings.parameterSettings![k] as StatefulParameterSetting)
266
+ .definition
267
+ .getSettings() as ArrayParameterSetting)
268
+ .isElementEqual ?? ((a, b) => a === b)
269
+ : (settings.parameterSettings![k] as ArrayParameterSetting)
270
+ .isElementEqual ?? ((a, b) => a === b)
263
271
 
264
272
  const desiredCopy = [...desiredArray];
265
273
  const currentCopy = [...v];
@@ -63,19 +63,16 @@ describe('Resource options parser tests', () => {
63
63
  schema,
64
64
  }
65
65
 
66
- expect(() => new ParsedResourceSettings(option)).throws;
66
+ expect(() => new ParsedResourceSettings(option)).toThrowError();
67
67
  })
68
68
 
69
69
  it('Throws an error when an import.requiredParameters is declared improperly', () => {
70
70
  const schema = {
71
71
  '$schema': 'http://json-schema.org/draft-07/schema',
72
- '$id': 'https://www.codifycli.com/git-clone.json',
73
- 'title': 'Git-clone resource',
74
72
  'type': 'object',
75
73
  'properties': {
76
74
  'remote': {
77
75
  'type': 'string',
78
- 'description': 'Remote tracking url to clone repo from. Equivalent to repository and only one should be specified'
79
76
  },
80
77
  },
81
78
  'additionalProperties': false,
@@ -89,6 +86,77 @@ describe('Resource options parser tests', () => {
89
86
  }
90
87
  }
91
88
 
92
- expect(() => new ParsedResourceSettings(option)).throws;
89
+ expect(() => new ParsedResourceSettings(option)).toThrowError();
90
+ })
91
+
92
+ it('Throws an error when an import.refreshKeys is declared improperly', () => {
93
+ const schema = {
94
+ '$schema': 'http://json-schema.org/draft-07/schema',
95
+ 'type': 'object',
96
+ 'properties': {
97
+ 'remote': {
98
+ 'type': 'string',
99
+ },
100
+ },
101
+ 'additionalProperties': false,
102
+ }
103
+
104
+ const option: ResourceSettings<TestConfig> = {
105
+ id: 'typeId',
106
+ schema,
107
+ import: {
108
+ refreshKeys: ['import-error']
109
+ }
110
+ }
111
+
112
+ expect(() => new ParsedResourceSettings(option)).toThrowError();
113
+ })
114
+
115
+ it('Doesn\'t throw an error when an import.refreshValues is declared properly', () => {
116
+ const schema = {
117
+ '$schema': 'http://json-schema.org/draft-07/schema',
118
+ 'type': 'object',
119
+ 'properties': {
120
+ 'remote': {
121
+ 'type': 'string',
122
+ },
123
+ },
124
+ 'additionalProperties': false,
125
+ }
126
+
127
+ const option: ResourceSettings<TestConfig> = {
128
+ id: 'typeId',
129
+ schema,
130
+ import: {
131
+ refreshKeys: ['remote'],
132
+ }
133
+ }
134
+
135
+ expect(() => new ParsedResourceSettings(option)).not.toThrowError()
136
+ })
137
+
138
+ it('Throws an error if defaultRefreshValue is not found in refreshKeys', () => {
139
+ const schema = {
140
+ '$schema': 'http://json-schema.org/draft-07/schema',
141
+ 'type': 'object',
142
+ 'properties': {
143
+ 'remote': {
144
+ 'type': 'string',
145
+ },
146
+ },
147
+ 'additionalProperties': false,
148
+ }
149
+
150
+ const option: ResourceSettings<TestConfig> = {
151
+ id: 'typeId',
152
+ schema,
153
+ import: {
154
+ defaultRefreshValues: {
155
+ repository: 'abc'
156
+ }
157
+ }
158
+ }
159
+
160
+ expect(() => new ParsedResourceSettings(option)).toThrowError()
93
161
  })
94
162
  })
@@ -167,11 +167,37 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
167
167
  )
168
168
  }
169
169
 
170
- const importRequiredParameterNotInSchema =
171
- this.settings.import?.requiredParameters?.filter((p) => !(schema?.properties[p]))
172
- if (schema && importRequiredParameterNotInSchema && importRequiredParameterNotInSchema.length > 0) {
173
- throw new Error(`The following properties were declared in settings.import.requiredParameters but were not found in the schema:
174
- ${JSON.stringify(importRequiredParameterNotInSchema, null, 2)}`)
170
+ if (this.settings.import) {
171
+ const { requiredParameters, refreshKeys, defaultRefreshValues } = this.settings.import;
172
+
173
+ const requiredParametersNotInSchema = requiredParameters
174
+ ?.filter(
175
+ (p) => schema && !(schema.properties[p])
176
+ )
177
+ if (schema && requiredParametersNotInSchema && requiredParametersNotInSchema.length > 0) {
178
+ throw new Error(`The following properties were declared in settings.import.requiredParameters but were not found in the schema:
179
+ ${JSON.stringify(requiredParametersNotInSchema, null, 2)}`)
180
+ }
181
+
182
+ const refreshKeyNotInSchema = refreshKeys
183
+ ?.filter(
184
+ (k) => schema && !(schema.properties[k])
185
+ )
186
+ if (schema && refreshKeyNotInSchema && refreshKeyNotInSchema.length > 0) {
187
+ throw new Error(`The following properties were declared in settings.import.refreshKeys but were not found in the schema:
188
+ ${JSON.stringify(requiredParametersNotInSchema, null, 2)}`)
189
+ }
190
+
191
+ const refreshValueNotInRefreshKey =
192
+ Object.entries(defaultRefreshValues ?? {})
193
+ .filter(
194
+ ([k]) => schema && !(schema.properties[k])
195
+ ).map(([k]) => k)
196
+
197
+ if (schema && refreshValueNotInRefreshKey.length > 0) {
198
+ throw new Error(`Properties declared in defaultRefreshValues must already be declared in refreshKeys:
199
+ ${JSON.stringify(refreshValueNotInRefreshKey, null, 2)}`)
200
+ }
175
201
  }
176
202
  }
177
203
 
@@ -190,19 +190,21 @@ export class ResourceController<T extends StringIndexedObject> {
190
190
  await this.applyTransformParameters(config);
191
191
 
192
192
  // Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
193
- const parametersToRefresh = this.settings.import?.refreshParameters
193
+ const parametersToRefresh = this.settings.import?.refreshKeys
194
194
  ? {
195
195
  ...Object.fromEntries(
196
- this.settings.import?.refreshParameters.map((k) => [k, null])
196
+ this.settings.import?.refreshKeys.map((k) => [k, null])
197
197
  ),
198
+ ...this.settings.import?.defaultRefreshValues,
198
199
  ...config,
199
200
  }
200
201
  : {
201
- ...Object.fromEntries(
202
- this.getAllParameterKeys().map((k) => [k, null])
203
- ),
204
- ...config,
205
- };
202
+ ...Object.fromEntries(
203
+ this.getAllParameterKeys().map((k) => [k, null])
204
+ ),
205
+ ...this.settings.import?.defaultRefreshValues,
206
+ ...config,
207
+ };
206
208
 
207
209
  // Parse data from the user supplied config
208
210
  const parsedConfig = new ConfigParser(parametersToRefresh, null, this.parsedSettings.statefulParameters)
@@ -612,4 +612,21 @@ describe('Resource parameter tests', () => {
612
612
 
613
613
  expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
614
614
  })
615
+
616
+ it('Accepts an input parameters for imports', () => {
617
+ const resource = new class extends TestResource {
618
+ getSettings(): ResourceSettings<TestConfig> {
619
+ return {
620
+ id: 'resourceType',
621
+ import: {
622
+ requiredParameters: ['propA'],
623
+ refreshKeys: ['propB', 'propA'],
624
+ defaultRefreshValues: {
625
+ propB: 6,
626
+ }
627
+ }
628
+ }
629
+ }
630
+ };
631
+ })
615
632
  })
@@ -65,10 +65,57 @@ export interface ResourceSettings<T extends StringIndexedObject> {
65
65
  */
66
66
  inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
67
67
 
68
+ /**
69
+ * Customize the import behavior of the resource. By default, <code>codify import</code> will call `refresh()` with
70
+ * every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
71
+ * in the schema and will prompt the user for these values before performing the import.
72
+ *
73
+ * <b>Example:</b><br>
74
+ * Resource `alias` with parameters
75
+ *
76
+ * ```
77
+ * { alias <b>(*required)</b>: string; value: string; }
78
+ * ```
79
+ *
80
+ * When the user calls `codify import alias`, they will first be prompted to enter the value for `alias`. Refresh
81
+ * is then called with `refresh({ alias: 'user-input', value: null })`. The result returned to the user will then be:
82
+ *
83
+ * ```
84
+ * { type: 'alias', alias: 'user-input', value: 'git push' }
85
+ * ```
86
+ */
68
87
  import?: {
88
+
89
+ /**
90
+ * Customize the required parameters needed to import this resource. By default, the `requiredParameters` are taken
91
+ * from the JSON schema. The `requiredParameters` parameter must be declared if a complex required is declared in
92
+ * the schema (contains `oneOf`, `anyOf`, `allOf`, `if`, `then`, `else`).
93
+ * <br>
94
+ * The user will be prompted for the required parameters before the import starts. This is done because for most resources
95
+ * the required parameters change the behaviour of the refresh (for example for the `alias` resource, the `alias` parmaeter
96
+ * chooses which alias the resource is managing).
97
+ *
98
+ * See {@link import} for more information on how importing works.
99
+ */
69
100
  requiredParameters?: Array<Partial<keyof T>>;
70
101
 
71
- refreshParameters?: Array<Partial<keyof T>>;
102
+ /**
103
+ * Customize which keys will be refreshed in the import. Typically, `refresh()` statements only refresh
104
+ * the parameters provided as the input. Use `refreshKeys` to control which parameter keys are passed in.
105
+ * <br>
106
+ * By default all parameters (except for {@link requiredParameters }) are passed in with the value `null`. The passed
107
+ * in value can be customized using {@link defaultRefreshValues}
108
+ *
109
+ * See {@link import} for more information on how importing works.
110
+ */
111
+ refreshKeys?: Array<Partial<keyof T>>;
112
+
113
+ /**
114
+ * Customize the value that is passed into refresh when importing. This must only contain keys found in {@link refreshKeys}.
115
+ *
116
+ * See {@link import} for more information on how importing works.
117
+ */
118
+ defaultRefreshValues?: Partial<T>
72
119
  }
73
120
  }
74
121
 
@@ -164,6 +211,26 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
164
211
  * @return Return true if desired is equivalent to current.
165
212
  */
166
213
  isElementEqual?: (desired: any, current: any) => boolean
214
+
215
+ /**
216
+ * Filter the contents of the refreshed array by the desired. This way items currently on the system but not
217
+ * in desired don't show up in the plan.
218
+ *
219
+ * <b>For example, for the nvm resource:</b>
220
+ * <ul>
221
+ * <li>Desired (20.18.0, 18.9.0, 16.3.1)</li>
222
+ * <li>Current (20.18.0, 22.1.3, 12.1.0)</li>
223
+ * </ul>
224
+ *
225
+ * Without filtering the plan will be:
226
+ * (~20.18.0, +18.9.0, +16.3.1, -22.1.3, -12.1.0)<br>
227
+ * With filtering the plan is: (~20.18.0, +18.9.0, +16.3.1)
228
+ *
229
+ * As you can see, filtering prevents items currently installed on the system from being removed.
230
+ *
231
+ * Defaults to true.
232
+ */
233
+ filterInStatelessMode?: boolean,
167
234
  }
168
235
 
169
236
  /**
@@ -151,8 +151,6 @@ describe('Stateful parameter tests', () => {
151
151
  nodeVersions: ['20.15'],
152
152
  } as any)
153
153
 
154
- console.log(JSON.stringify(plan, null, 2))
155
-
156
154
  expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
157
155
  })
158
156
  })