Skip to content

Commit 28bd520

Browse files
committed
feat: Added itemType parameter, it's like type but for items of arrays (sets default transformation and isElementEqual fns)
1 parent 9dc7ad6 commit 28bd520

File tree

10 files changed

+152
-40
lines changed

10 files changed

+152
-40
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codify-plugin-lib",
3-
"version": "1.0.131",
3+
"version": "1.0.132",
44
"description": "Library plugin library",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",

src/plan/plan.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -384,15 +384,15 @@ export class Plan<T extends StringIndexedObject> {
384384
const defaultFilterMethod = ((desired: any[], current: any[]) => {
385385
const result = [];
386386

387-
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
388-
const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2))
387+
for (let counter = desired.length - 1; counter >= 0; counter--) {
388+
const idx = currentCopy.findIndex((e2) => matcher(desired[counter], e2))
389389

390390
if (idx === -1) {
391391
continue;
392392
}
393393

394-
desiredCopy.splice(counter, 1)
395-
const [element] = currentCopy.splice(idx, 1)
394+
desired.splice(counter, 1)
395+
const [element] = current.splice(idx, 1)
396396
result.push(element)
397397
}
398398

@@ -413,9 +413,7 @@ export class Plan<T extends StringIndexedObject> {
413413
return this.changeSet.operation !== ResourceOperation.NOOP;
414414
}
415415

416-
/**
417-
* Convert the plan to a JSON response object
418-
*/
416+
/** Convert the plan to a JSON response object */
419417
toResponse(): PlanResponseData {
420418
return {
421419
planId: this.id,

src/plugin/plugin.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ describe('Plugin tests', () => {
194194
return {
195195
id: 'typeId',
196196
schema,
197-
import: {
197+
importAndDestroy: {
198198
requiredParameters: []
199199
}
200200
}

src/plugin/plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class Plugin {
6767

6868
const schema = resource.settings.schema as JSONSchemaType<any> | undefined;
6969
const requiredPropertyNames = (
70-
resource.settings.import?.requiredParameters
70+
resource.settings.importAndDestroy?.requiredParameters
7171
?? schema?.required
7272
?? null
7373
) as null | string[];

src/resource/parsed-resource-settings.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('Resource options parser tests', () => {
8181
const option: ResourceSettings<TestConfig> = {
8282
id: 'typeId',
8383
schema,
84-
import: {
84+
importAndDestroy: {
8585
requiredParameters: ['import-error']
8686
}
8787
}
@@ -104,7 +104,7 @@ describe('Resource options parser tests', () => {
104104
const option: ResourceSettings<TestConfig> = {
105105
id: 'typeId',
106106
schema,
107-
import: {
107+
importAndDestroy: {
108108
refreshKeys: ['import-error']
109109
}
110110
}
@@ -127,7 +127,7 @@ describe('Resource options parser tests', () => {
127127
const option: ResourceSettings<TestConfig> = {
128128
id: 'typeId',
129129
schema,
130-
import: {
130+
importAndDestroy: {
131131
refreshKeys: ['remote'],
132132
}
133133
}
@@ -150,7 +150,7 @@ describe('Resource options parser tests', () => {
150150
const option: ResourceSettings<TestConfig> = {
151151
id: 'typeId',
152152
schema,
153-
import: {
153+
importAndDestroy: {
154154
defaultRefreshValues: {
155155
repository: 'abc'
156156
}

src/resource/parsed-resource-settings.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
ArrayParameterSetting,
77
DefaultParameterSetting,
88
ParameterSetting,
9+
resolveElementEqualsFn,
910
resolveEqualsFn,
10-
resolveFnFromEqualsFnOrString,
1111
resolveParameterTransformFn,
1212
ResourceSettings,
1313
StatefulParameterSetting
@@ -95,8 +95,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
9595
if (v.type === 'array') {
9696
const parsed = {
9797
...v,
98-
isElementEqual: resolveFnFromEqualsFnOrString((v as ArrayParameterSetting).isElementEqual)
99-
?? ((a: unknown, b: unknown) => a === b),
98+
isElementEqual: resolveElementEqualsFn(v as ArrayParameterSetting)
10099
}
101100

102101
return [k, parsed as ParsedArrayParameterSetting];
@@ -186,7 +185,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
186185
}
187186

188187
const schema = this.settings.schema as JSONSchemaType<any>;
189-
if (!this.settings.import && (schema?.oneOf
188+
if (!this.settings.importAndDestroy && (schema?.oneOf
190189
&& Array.isArray(schema.oneOf)
191190
&& schema.oneOf.some((s) => s.required)
192191
)
@@ -214,8 +213,8 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
214213
)
215214
}
216215

217-
if (this.settings.import) {
218-
const { requiredParameters, refreshKeys, defaultRefreshValues } = this.settings.import;
216+
if (this.settings.importAndDestroy) {
217+
const { requiredParameters, refreshKeys, defaultRefreshValues } = this.settings.importAndDestroy;
219218

220219
const requiredParametersNotInSchema = requiredParameters
221220
?.filter(

src/resource/resource-controller.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,33 @@ export class ResourceController<T extends StringIndexedObject> {
158158
})
159159
}
160160

161+
async planDestroy(
162+
core: ResourceConfig,
163+
parameters: Partial<T>
164+
): Promise<Plan<T>> {
165+
this.addDefaultValues(parameters);
166+
await this.applyTransformParameters(parameters);
167+
168+
// Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
169+
const parametersToRefresh = this.settings.importAndDestroy?.refreshKeys
170+
? {
171+
...Object.fromEntries(
172+
this.settings.importAndDestroy?.refreshKeys.map((k) => [k, null])
173+
),
174+
...this.settings.importAndDestroy?.defaultRefreshValues,
175+
...parameters,
176+
}
177+
: {
178+
...Object.fromEntries(
179+
this.getAllParameterKeys().map((k) => [k, null])
180+
),
181+
...this.settings.importAndDestroy?.defaultRefreshValues,
182+
...parameters,
183+
};
184+
185+
return this.plan(core, null, parametersToRefresh, true);
186+
}
187+
161188
async apply(plan: Plan<T>): Promise<void> {
162189
if (plan.getResourceType() !== this.typeId) {
163190
throw new Error(`Internal error: Plan set to wrong resource during apply. Expected ${this.typeId} but got: ${plan.getResourceType()}`);
@@ -191,19 +218,19 @@ export class ResourceController<T extends StringIndexedObject> {
191218
await this.applyTransformParameters(parameters);
192219

193220
// Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
194-
const parametersToRefresh = this.settings.import?.refreshKeys
221+
const parametersToRefresh = this.settings.importAndDestroy?.refreshKeys
195222
? {
196223
...Object.fromEntries(
197-
this.settings.import?.refreshKeys.map((k) => [k, null])
224+
this.settings.importAndDestroy?.refreshKeys.map((k) => [k, null])
198225
),
199-
...this.settings.import?.defaultRefreshValues,
226+
...this.settings.importAndDestroy?.defaultRefreshValues,
200227
...parameters,
201228
}
202229
: {
203230
...Object.fromEntries(
204231
this.getAllParameterKeys().map((k) => [k, null])
205232
),
206-
...this.settings.import?.defaultRefreshValues,
233+
...this.settings.importAndDestroy?.defaultRefreshValues,
207234
...parameters,
208235
};
209236

src/resource/resource-settings.test.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ describe('Resource parameter tests', () => {
637637
getSettings(): ResourceSettings<TestConfig> {
638638
return {
639639
id: 'resourceType',
640-
import: {
640+
importAndDestroy: {
641641
requiredParameters: [
642642
'propA',
643643
'propB',
@@ -723,7 +723,7 @@ describe('Resource parameter tests', () => {
723723
getSettings(): ResourceSettings<TestConfig> {
724724
return {
725725
id: 'resourceType',
726-
import: {
726+
importAndDestroy: {
727727
requiredParameters: ['propA'],
728728
refreshKeys: ['propB', 'propA'],
729729
defaultRefreshValues: {
@@ -969,4 +969,59 @@ describe('Resource parameter tests', () => {
969969
);
970970

971971
})
972+
973+
it('Supports equality check for itemType', async () => {
974+
const resource = new class extends TestResource {
975+
getSettings(): ResourceSettings<TestConfig> {
976+
return {
977+
id: 'resourceType',
978+
parameterSettings: {
979+
propA: { type: 'array', itemType: 'version' }
980+
}
981+
}
982+
}
983+
984+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
985+
return {
986+
propA: ['10.0.0']
987+
}
988+
}
989+
};
990+
991+
const controller = new ResourceController(resource);
992+
993+
const result = await controller.plan({ type: 'resourceType' }, { propA: ['10.0'] }, null, false);
994+
expect(result.changeSet).toMatchObject({
995+
operation: ResourceOperation.NOOP,
996+
})
997+
})
998+
999+
it('Supports transformations for itemType', async () => {
1000+
const home = os.homedir()
1001+
const testPath = path.join(home, 'test/folder');
1002+
1003+
const resource = new class extends TestResource {
1004+
getSettings(): ResourceSettings<TestConfig> {
1005+
return {
1006+
id: 'resourceType',
1007+
parameterSettings: {
1008+
propA: { type: 'array', itemType: 'directory' }
1009+
}
1010+
}
1011+
}
1012+
1013+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
1014+
return {
1015+
propA: [testPath]
1016+
}
1017+
}
1018+
};
1019+
1020+
const controller = new ResourceController(resource);
1021+
1022+
const result = await controller.plan({ type: 'resourceType' }, { propA: ['~/test/folder'] }, null, false);
1023+
expect(result.changeSet).toMatchObject({
1024+
operation: ResourceOperation.NOOP,
1025+
})
1026+
})
9721027
})

src/resource/resource-settings.ts

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ export interface ResourceSettings<T extends StringIndexedObject> {
6767
inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
6868

6969
/**
70-
* Customize the import behavior of the resource. By default, <code>codify import</code> will call `refresh()` with
71-
* every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
72-
* in the schema and will prompt the user for these values before performing the import.
70+
* Customize the import and destory behavior of the resource. By default, <code>codify import</code> and <code>codify destroy</code> will call
71+
* `refresh()` with every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
72+
* in the schema and will prompt the user for these values before performing the import or destroy.
7373
*
7474
* <b>Example:</b><br>
7575
* Resource `alias` with parameters
@@ -85,7 +85,7 @@ export interface ResourceSettings<T extends StringIndexedObject> {
8585
* { type: 'alias', alias: 'user-input', value: 'git push' }
8686
* ```
8787
*/
88-
import?: {
88+
importAndDestroy?: {
8989

9090
/**
9191
* Customize the required parameters needed to import this resource. By default, the `requiredParameters` are taken
@@ -96,7 +96,7 @@ export interface ResourceSettings<T extends StringIndexedObject> {
9696
* the required parameters change the behaviour of the refresh (for example for the `alias` resource, the `alias` parmaeter
9797
* chooses which alias the resource is managing).
9898
*
99-
* See {@link import} for more information on how importing works.
99+
* See {@link importAndDestroy} for more information on how importing works.
100100
*/
101101
requiredParameters?: Array<Partial<keyof T>>;
102102

@@ -107,14 +107,14 @@ export interface ResourceSettings<T extends StringIndexedObject> {
107107
* By default all parameters (except for {@link requiredParameters }) are passed in with the value `null`. The passed
108108
* in value can be customized using {@link defaultRefreshValues}
109109
*
110-
* See {@link import} for more information on how importing works.
110+
* See {@link importAndDestroy} for more information on how importing works.
111111
*/
112112
refreshKeys?: Array<Partial<keyof T>>;
113113

114114
/**
115115
* Customize the value that is passed into refresh when importing. This must only contain keys found in {@link refreshKeys}.
116116
*
117-
* See {@link import} for more information on how importing works.
117+
* See {@link importAndDestroy} for more information on how importing works.
118118
*/
119119
defaultRefreshValues?: Partial<T>
120120
}
@@ -233,6 +233,12 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
233233
* Defaults to true.
234234
*/
235235
filterInStatelessMode?: ((desired: any[], current: any[]) => any[]) | boolean,
236+
237+
/**
238+
* The type of the array item. See {@link ParameterSettingType} for the available options. This value
239+
* is mainly used to determine the equality method when performing diffing.
240+
*/
241+
itemType?: ParameterSettingType,
236242
}
237243

238244
/**
@@ -273,10 +279,7 @@ export function resolveEqualsFn(parameter: ParameterSetting): (desired: unknown,
273279
const isEqual = resolveFnFromEqualsFnOrString(parameter.isEqual);
274280

275281
if (parameter.type === 'array') {
276-
const arrayParameter = parameter as ArrayParameterSetting;
277-
const isElementEqual = resolveFnFromEqualsFnOrString(arrayParameter.isElementEqual);
278-
279-
return isEqual ?? areArraysEqual.bind(areArraysEqual, isElementEqual)
282+
return isEqual ?? areArraysEqual.bind(areArraysEqual, resolveElementEqualsFn(parameter as ArrayParameterSetting))
280283
}
281284

282285
if (parameter.type === 'stateful') {
@@ -286,6 +289,21 @@ export function resolveEqualsFn(parameter: ParameterSetting): (desired: unknown,
286289
return isEqual ?? ParameterEqualsDefaults[parameter.type as ParameterSettingType] ?? (((a, b) => a === b));
287290
}
288291

292+
export function resolveElementEqualsFn(parameter: ArrayParameterSetting): (desired: unknown, current: unknown) => boolean {
293+
if (parameter.isElementEqual) {
294+
const elementEq = resolveFnFromEqualsFnOrString(parameter.isElementEqual);
295+
if (elementEq) {
296+
return elementEq;
297+
}
298+
}
299+
300+
if (parameter.itemType && ParameterEqualsDefaults[parameter.itemType]) {
301+
return ParameterEqualsDefaults[parameter.itemType]!
302+
}
303+
304+
return (a, b) => a === b;
305+
}
306+
289307
// This resolves the fn if it is a string.
290308
// A string can be specified to use a default equals method
291309
export function resolveFnFromEqualsFnOrString(
@@ -312,10 +330,26 @@ const ParameterTransformationDefaults: Partial<Record<ParameterSettingType, (inp
312330
: a;
313331
},
314332
'string': String,
333+
// TODO: Add a array parameter itemType parameter
334+
// 'array': (arr: unknown[]) => arr.map((i) => (parameter as ArrayParameterSetting).itemType ? ParameterTransformationDefaults[])
315335
}
316336

317337
export function resolveParameterTransformFn(
318338
parameter: ParameterSetting
319339
): ((input: any, parameter: ParameterSetting) => Promise<any> | any) | undefined {
340+
341+
if (parameter.type === 'array'
342+
&& (parameter as ArrayParameterSetting).itemType
343+
&& ParameterTransformationDefaults[(parameter as ArrayParameterSetting).itemType!]
344+
&& !parameter.inputTransformation
345+
) {
346+
const itemType = (parameter as ArrayParameterSetting).itemType!;
347+
const itemTransformation = ParameterTransformationDefaults[itemType]!;
348+
349+
return (input: unknown[], parameter) => {
350+
return input.map((i) => itemTransformation(i, parameter))
351+
}
352+
}
353+
320354
return parameter.inputTransformation ?? ParameterTransformationDefaults[parameter.type as ParameterSettingType] ?? undefined;
321355
}

0 commit comments

Comments
 (0)