codify-plugin-lib 1.0.134 → 1.0.136

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.
@@ -1,7 +1,7 @@
1
1
  import { JSONSchemaType } from 'ajv';
2
2
  import { StringIndexedObject } from 'codify-schemas';
3
3
  import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
4
- import { ArrayParameterSetting, DefaultParameterSetting, ParameterSetting, ResourceSettings } from './resource-settings.js';
4
+ import { ArrayParameterSetting, DefaultParameterSetting, InputTransformation, ResourceSettings } from './resource-settings.js';
5
5
  export interface ParsedStatefulParameterSetting extends DefaultParameterSetting {
6
6
  type: 'stateful';
7
7
  controller: StatefulParameterController<any, unknown>;
@@ -25,14 +25,14 @@ export declare class ParsedResourceSettings<T extends StringIndexedObject> imple
25
25
  } | boolean;
26
26
  removeStatefulParametersBeforeDestroy?: boolean | undefined;
27
27
  dependencies?: string[] | undefined;
28
- inputTransformation?: ((desired: Partial<T>) => unknown) | undefined;
28
+ transformation?: InputTransformation;
29
29
  private settings;
30
30
  constructor(settings: ResourceSettings<T>);
31
31
  get typeId(): string;
32
32
  get statefulParameters(): Map<keyof T, StatefulParameterController<T, T[keyof T]>>;
33
33
  get parameterSettings(): Record<keyof T, ParsedParameterSetting>;
34
34
  get defaultValues(): Partial<Record<keyof T, unknown>>;
35
- get inputTransformations(): Partial<Record<keyof T, (a: unknown, parameter: ParameterSetting) => unknown>>;
35
+ get inputTransformations(): Partial<Record<keyof T, InputTransformation>>;
36
36
  get statefulParameterOrder(): Map<keyof T, number>;
37
37
  private validateSettings;
38
38
  private validateParameterEqualsFn;
@@ -7,7 +7,7 @@ export class ParsedResourceSettings {
7
7
  allowMultiple;
8
8
  removeStatefulParametersBeforeDestroy;
9
9
  dependencies;
10
- inputTransformation;
10
+ transformation;
11
11
  settings;
12
12
  constructor(settings) {
13
13
  this.settings = settings;
@@ -16,7 +16,7 @@ export class ParsedResourceSettings {
16
16
  this.allowMultiple = settings.allowMultiple;
17
17
  this.removeStatefulParametersBeforeDestroy = settings.removeStatefulParametersBeforeDestroy;
18
18
  this.dependencies = settings.dependencies;
19
- this.inputTransformation = settings.inputTransformation;
19
+ this.transformation = settings.transformation;
20
20
  this.validateSettings();
21
21
  }
22
22
  get typeId() {
@@ -83,7 +83,7 @@ export class ParsedResourceSettings {
83
83
  }
84
84
  return Object.fromEntries(Object.entries(this.settings.parameterSettings)
85
85
  .filter(([_, v]) => resolveParameterTransformFn(v) !== undefined)
86
- .map(([k, v]) => [k, resolveParameterTransformFn(v).to]));
86
+ .map(([k, v]) => [k, resolveParameterTransformFn(v)]));
87
87
  });
88
88
  }
89
89
  get statefulParameterOrder() {
@@ -174,7 +174,9 @@ export class ResourceController {
174
174
  ?? null;
175
175
  }
176
176
  const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, parametersToRefresh);
177
- return [{ core, parameters: { ...currentParametersArray[0], ...statefulCurrentParameters } }];
177
+ const resultParameters = { ...currentParametersArray[0], ...statefulCurrentParameters };
178
+ await this.applyTransformParameters(resultParameters, true);
179
+ return [{ core, parameters: resultParameters }];
178
180
  }
179
181
  async applyCreate(plan) {
180
182
  await this.resource.create(plan);
@@ -236,7 +238,7 @@ ${JSON.stringify(refresh, null, 2)}
236
238
  `);
237
239
  }
238
240
  }
239
- async applyTransformParameters(config) {
241
+ async applyTransformParameters(config, reverse = false) {
240
242
  if (!config) {
241
243
  return;
242
244
  }
@@ -244,10 +246,14 @@ ${JSON.stringify(refresh, null, 2)}
244
246
  if (config[key] === undefined || !inputTransformation) {
245
247
  continue;
246
248
  }
247
- config[key] = await inputTransformation(config[key], this.settings.parameterSettings[key]);
249
+ config[key] = reverse
250
+ ? await inputTransformation.from(config[key])
251
+ : await inputTransformation.to(config[key]);
248
252
  }
249
- if (this.settings.inputTransformation) {
250
- const transformed = await this.settings.inputTransformation({ ...config });
253
+ if (this.settings.transformation) {
254
+ const transformed = reverse
255
+ ? await this.settings.transformation.from({ ...config })
256
+ : await this.settings.transformation.to({ ...config });
251
257
  Object.keys(config).forEach((k) => delete config[k]);
252
258
  Object.assign(config, transformed);
253
259
  }
@@ -66,7 +66,7 @@ export interface ResourceSettings<T extends StringIndexedObject> {
66
66
  *
67
67
  * @param desired
68
68
  */
69
- inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
69
+ transformation?: InputTransformation;
70
70
  /**
71
71
  * Customize the import and destory behavior of the resource. By default, <code>codify import</code> and <code>codify destroy</code> will call
72
72
  * `refresh()` with every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
@@ -151,7 +151,7 @@ export interface DefaultParameterSetting {
151
151
  *
152
152
  * @param input The original parameter value from the desired config.
153
153
  */
154
- inputTransformation?: InputTransformation;
154
+ transformation?: InputTransformation;
155
155
  /**
156
156
  * Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
157
157
  * This value will override the pre-set equality function from the type. Return true if the desired value is
@@ -54,17 +54,17 @@ const ParameterTransformationDefaults = {
54
54
  }
55
55
  };
56
56
  export function resolveParameterTransformFn(parameter) {
57
- if (parameter.type === 'stateful' && !parameter.inputTransformation) {
57
+ if (parameter.type === 'stateful' && !parameter.transformation) {
58
58
  const sp = parameter.definition.getSettings();
59
- if (sp.inputTransformation) {
60
- return parameter.definition?.getSettings()?.inputTransformation;
59
+ if (sp.transformation) {
60
+ return parameter.definition?.getSettings()?.transformation;
61
61
  }
62
62
  return sp.type ? ParameterTransformationDefaults[sp.type] : undefined;
63
63
  }
64
64
  if (parameter.type === 'array'
65
65
  && parameter.itemType
66
66
  && ParameterTransformationDefaults[parameter.itemType]
67
- && !parameter.inputTransformation) {
67
+ && !parameter.transformation) {
68
68
  const itemType = parameter.itemType;
69
69
  const itemTransformation = ParameterTransformationDefaults[itemType];
70
70
  return {
@@ -76,5 +76,5 @@ export function resolveParameterTransformFn(parameter) {
76
76
  }
77
77
  };
78
78
  }
79
- return parameter.inputTransformation ?? ParameterTransformationDefaults[parameter.type] ?? undefined;
79
+ return parameter.transformation ?? ParameterTransformationDefaults[parameter.type] ?? undefined;
80
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codify-plugin-lib",
3
- "version": "1.0.134",
3
+ "version": "1.0.136",
4
4
  "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -5,6 +5,7 @@ import { StatefulParameterController } from '../stateful-parameter/stateful-para
5
5
  import {
6
6
  ArrayParameterSetting,
7
7
  DefaultParameterSetting,
8
+ InputTransformation,
8
9
  ParameterSetting,
9
10
  resolveElementEqualsFn,
10
11
  resolveEqualsFn,
@@ -43,7 +44,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
43
44
 
44
45
  removeStatefulParametersBeforeDestroy?: boolean | undefined;
45
46
  dependencies?: string[] | undefined;
46
- inputTransformation?: ((desired: Partial<T>) => unknown) | undefined;
47
+ transformation?: InputTransformation;
47
48
  private settings: ResourceSettings<T>;
48
49
 
49
50
  constructor(settings: ResourceSettings<T>) {
@@ -53,7 +54,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
53
54
  this.allowMultiple = settings.allowMultiple;
54
55
  this.removeStatefulParametersBeforeDestroy = settings.removeStatefulParametersBeforeDestroy;
55
56
  this.dependencies = settings.dependencies;
56
- this.inputTransformation = settings.inputTransformation;
57
+ this.transformation = settings.transformation;
57
58
 
58
59
  this.validateSettings();
59
60
  }
@@ -136,7 +137,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
136
137
  });
137
138
  }
138
139
 
139
- get inputTransformations(): Partial<Record<keyof T, (a: unknown, parameter: ParameterSetting) => unknown>> {
140
+ get inputTransformations(): Partial<Record<keyof T, InputTransformation>> {
140
141
  return this.getFromCacheOrCreate('inputTransformations', () => {
141
142
  if (!this.settings.parameterSettings) {
142
143
  return {};
@@ -145,8 +146,8 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
145
146
  return Object.fromEntries(
146
147
  Object.entries(this.settings.parameterSettings)
147
148
  .filter(([_, v]) => resolveParameterTransformFn(v!) !== undefined)
148
- .map(([k, v]) => [k, resolveParameterTransformFn(v!)!.to] as const)
149
- ) as Record<keyof T, (a: unknown) => unknown>;
149
+ .map(([k, v]) => [k, resolveParameterTransformFn(v!)] as const)
150
+ ) as Record<keyof T, InputTransformation>;
150
151
  });
151
152
  }
152
153
 
@@ -7,7 +7,7 @@ import { CreatePlan, DestroyPlan, ModifyPlan } from '../plan/plan-types.js';
7
7
  import { ParameterChange } from '../plan/change-set.js';
8
8
  import { ResourceController } from './resource-controller.js';
9
9
  import { TestConfig, testPlan, TestResource, TestStatefulParameter } from '../utils/test-utils.test.js';
10
- import { untildify } from '../utils/utils.js';
10
+ import { tildify, untildify } from '../utils/utils.js';
11
11
  import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
12
12
  import { Plan } from '../plan/plan.js';
13
13
 
@@ -20,9 +20,8 @@ describe('Resource tests', () => {
20
20
  id: 'type',
21
21
  dependencies: ['homebrew', 'python'],
22
22
  parameterSettings: {
23
- propA: { canModify: true, inputTransformation: (input) => untildify(input) },
23
+ propA: { canModify: true, transformation: { to: (input) => untildify(input), from: (input) => tildify(input) } },
24
24
  },
25
- inputTransformation: (config) => ({ propA: config.propA, propC: config.propB }),
26
25
  }
27
26
  }
28
27
 
@@ -535,4 +534,178 @@ describe('Resource tests', () => {
535
534
  expect(parameter1.modify.calledOnce).to.be.true;
536
535
  expect(parameter2.addItem.calledOnce).to.be.true;
537
536
  });
537
+
538
+ it('Applies reverse input transformations for imports', async () => {
539
+ const resource = new class extends TestResource {
540
+ getSettings(): ResourceSettings<TestConfig> {
541
+ return {
542
+ id: 'resourceType',
543
+ parameterSettings: {
544
+ propD: {
545
+ type: 'array',
546
+ transformation: {
547
+ to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
548
+ Object.entries(h)
549
+ .map(([k, v]) => [
550
+ k,
551
+ typeof v === 'boolean'
552
+ ? (v ? 'yes' : 'no') // The file takes 'yes' or 'no' instead of booleans
553
+ : v,
554
+ ])
555
+ )
556
+ ),
557
+ from: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
558
+ Object.entries(h)
559
+ .map(([k, v]) => [
560
+ k,
561
+ v === 'yes' || v === 'no'
562
+ ? (v === 'yes')
563
+ : v,
564
+ ])
565
+ ))
566
+ }
567
+ }
568
+ }
569
+ }
570
+ }
571
+
572
+ async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
573
+ return {
574
+ propD: [
575
+ {
576
+ Host: 'new.com',
577
+ AddKeysToAgent: true,
578
+ IdentityFile: 'id_ed25519'
579
+ },
580
+ {
581
+ Host: 'github.com',
582
+ AddKeysToAgent: true,
583
+ UseKeychain: true,
584
+ },
585
+ {
586
+ Match: 'User bob,joe,phil',
587
+ PasswordAuthentication: true,
588
+ }
589
+ ],
590
+ }
591
+ }
592
+ }
593
+
594
+ const controller = new ResourceController(resource);
595
+ const plan = await controller.import({ type: 'resourceType' }, {});
596
+
597
+ expect(plan![0]).toMatchObject({
598
+ 'core': {
599
+ 'type': 'resourceType'
600
+ },
601
+ 'parameters': {
602
+ 'propD': [
603
+ {
604
+ 'Host': 'new.com',
605
+ 'AddKeysToAgent': true,
606
+ 'IdentityFile': 'id_ed25519'
607
+ },
608
+ {
609
+ 'Host': 'github.com',
610
+ 'AddKeysToAgent': true,
611
+ 'UseKeychain': true
612
+ },
613
+ {
614
+ 'Match': 'User bob,joe,phil',
615
+ 'PasswordAuthentication': true
616
+ }
617
+ ]
618
+ }
619
+ })
620
+ })
621
+
622
+ it('Applies reverse input transformations for imports (object level)', async () => {
623
+ const resource = new class extends TestResource {
624
+ getSettings(): ResourceSettings<TestConfig> {
625
+ return {
626
+ id: 'resourceType',
627
+ parameterSettings: {
628
+ propD: {
629
+ type: 'array',
630
+ }
631
+ },
632
+ transformation: {
633
+ to: (input: any) => ({
634
+ ...input,
635
+ propD: input.propD?.map((h) => Object.fromEntries(
636
+ Object.entries(h)
637
+ .map(([k, v]) => [
638
+ k,
639
+ typeof v === 'boolean'
640
+ ? (v ? 'yes' : 'no') // The file takes 'yes' or 'no' instead of booleans
641
+ : v,
642
+ ])
643
+ )
644
+ )
645
+ }),
646
+ from: (output: any) => ({
647
+ ...output,
648
+ propD: output.propD?.map((h) => Object.fromEntries(
649
+ Object.entries(h)
650
+ .map(([k, v]) => [
651
+ k,
652
+ v === 'yes' || v === 'no'
653
+ ? (v === 'yes')
654
+ : v,
655
+ ])
656
+ ))
657
+ })
658
+ }
659
+ }
660
+ }
661
+
662
+ async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
663
+ return {
664
+ propD: [
665
+ {
666
+ Host: 'new.com',
667
+ AddKeysToAgent: true,
668
+ IdentityFile: 'id_ed25519'
669
+ },
670
+ {
671
+ Host: 'github.com',
672
+ AddKeysToAgent: true,
673
+ UseKeychain: true,
674
+ },
675
+ {
676
+ Match: 'User bob,joe,phil',
677
+ PasswordAuthentication: true,
678
+ }
679
+ ],
680
+ }
681
+ }
682
+ }
683
+
684
+ const controller = new ResourceController(resource);
685
+ const plan = await controller.import({ type: 'resourceType' }, {});
686
+
687
+ expect(plan![0]).toMatchObject({
688
+ 'core': {
689
+ 'type': 'resourceType'
690
+ },
691
+ 'parameters': {
692
+ 'propD': [
693
+ {
694
+ 'Host': 'new.com',
695
+ 'AddKeysToAgent': true,
696
+ 'IdentityFile': 'id_ed25519'
697
+ },
698
+ {
699
+ 'Host': 'github.com',
700
+ 'AddKeysToAgent': true,
701
+ 'UseKeychain': true
702
+ },
703
+ {
704
+ 'Match': 'User bob,joe,phil',
705
+ 'PasswordAuthentication': true
706
+ }
707
+ ]
708
+ }
709
+ })
710
+ })
538
711
  });
@@ -254,7 +254,10 @@ export class ResourceController<T extends StringIndexedObject> {
254
254
  }
255
255
 
256
256
  const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, parametersToRefresh);
257
- return [{ core, parameters: { ...currentParametersArray[0], ...statefulCurrentParameters } }];
257
+ const resultParameters = { ...currentParametersArray[0], ...statefulCurrentParameters };
258
+
259
+ await this.applyTransformParameters(resultParameters, true);
260
+ return [{ core, parameters: resultParameters }];
258
261
  }
259
262
 
260
263
  private async applyCreate(plan: Plan<T>): Promise<void> {
@@ -333,7 +336,7 @@ ${JSON.stringify(refresh, null, 2)}
333
336
  }
334
337
  }
335
338
 
336
- private async applyTransformParameters(config: Partial<T> | null): Promise<void> {
339
+ private async applyTransformParameters(config: Partial<T> | null, reverse = false): Promise<void> {
337
340
  if (!config) {
338
341
  return;
339
342
  }
@@ -343,11 +346,16 @@ ${JSON.stringify(refresh, null, 2)}
343
346
  continue;
344
347
  }
345
348
 
346
- (config as Record<string, unknown>)[key] = await inputTransformation(config[key], this.settings.parameterSettings![key]!);
349
+ (config as Record<string, unknown>)[key] = reverse
350
+ ? await inputTransformation.from(config[key])
351
+ : await inputTransformation.to(config[key]);
347
352
  }
348
353
 
349
- if (this.settings.inputTransformation) {
350
- const transformed = await this.settings.inputTransformation({ ...config })
354
+ if (this.settings.transformation) {
355
+ const transformed = reverse
356
+ ? await this.settings.transformation.from({ ...config })
357
+ : await this.settings.transformation.to({ ...config })
358
+
351
359
  Object.keys(config).forEach((k) => delete config[k])
352
360
  Object.assign(config, transformed);
353
361
  }
@@ -569,10 +569,16 @@ describe('Resource parameter tests', () => {
569
569
  getSettings(): ResourceSettings<TestConfig> {
570
570
  return {
571
571
  id: 'resourceType',
572
- inputTransformation: (desired) => ({
573
- propA: 'propA',
574
- propB: 10,
575
- })
572
+ transformation: {
573
+ to: (desired) => ({
574
+ propA: 'propA',
575
+ propB: 10,
576
+ }),
577
+ from: (current) => ({
578
+ propA: 'propA',
579
+ propB: 10,
580
+ })
581
+ }
576
582
  }
577
583
  }
578
584
 
@@ -604,10 +610,16 @@ describe('Resource parameter tests', () => {
604
610
  getSettings(): ResourceSettings<TestConfig> {
605
611
  return {
606
612
  id: 'resourceType',
607
- inputTransformation: (desired) => ({
608
- propA: 'propA',
609
- propB: 10,
610
- })
613
+ transformation: {
614
+ to: (desired) => ({
615
+ propA: 'propA',
616
+ propB: 10,
617
+ }),
618
+ from: (desired) => ({
619
+ propA: 'propA',
620
+ propB: 10,
621
+ })
622
+ }
611
623
  }
612
624
  }
613
625
 
@@ -852,7 +864,7 @@ describe('Resource parameter tests', () => {
852
864
  parameterSettings: {
853
865
  propD: {
854
866
  type: 'array',
855
- inputTransformation: {
867
+ transformation: {
856
868
  to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
857
869
  Object.entries(h)
858
870
  .map(([k, v]) => [
@@ -918,7 +930,7 @@ describe('Resource parameter tests', () => {
918
930
  getSettings(): any {
919
931
  return {
920
932
  type: 'array',
921
- inputTransformation: {
933
+ transformation: {
922
934
  to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
923
935
  Object.entries(h)
924
936
  .map(([k, v]) => [
@@ -81,7 +81,7 @@ export interface ResourceSettings<T extends StringIndexedObject> {
81
81
  *
82
82
  * @param desired
83
83
  */
84
- inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
84
+ transformation?: InputTransformation;
85
85
 
86
86
  /**
87
87
  * Customize the import and destory behavior of the resource. By default, <code>codify import</code> and <code>codify destroy</code> will call
@@ -189,7 +189,7 @@ export interface DefaultParameterSetting {
189
189
  *
190
190
  * @param input The original parameter value from the desired config.
191
191
  */
192
- inputTransformation?: InputTransformation;
192
+ transformation?: InputTransformation;
193
193
 
194
194
  /**
195
195
  * Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
@@ -354,10 +354,10 @@ export function resolveParameterTransformFn(
354
354
  parameter: ParameterSetting
355
355
  ): InputTransformation | undefined {
356
356
 
357
- if (parameter.type === 'stateful' && !parameter.inputTransformation) {
357
+ if (parameter.type === 'stateful' && !parameter.transformation) {
358
358
  const sp = (parameter as StatefulParameterSetting).definition.getSettings();
359
- if (sp.inputTransformation) {
360
- return (parameter as StatefulParameterSetting).definition?.getSettings()?.inputTransformation
359
+ if (sp.transformation) {
360
+ return (parameter as StatefulParameterSetting).definition?.getSettings()?.transformation
361
361
  }
362
362
 
363
363
  return sp.type ? ParameterTransformationDefaults[sp.type] : undefined;
@@ -366,7 +366,7 @@ export function resolveParameterTransformFn(
366
366
  if (parameter.type === 'array'
367
367
  && (parameter as ArrayParameterSetting).itemType
368
368
  && ParameterTransformationDefaults[(parameter as ArrayParameterSetting).itemType!]
369
- && !parameter.inputTransformation
369
+ && !parameter.transformation
370
370
  ) {
371
371
  const itemType = (parameter as ArrayParameterSetting).itemType!;
372
372
  const itemTransformation = ParameterTransformationDefaults[itemType]!;
@@ -381,5 +381,5 @@ export function resolveParameterTransformFn(
381
381
  }
382
382
  }
383
383
 
384
- return parameter.inputTransformation ?? ParameterTransformationDefaults[parameter.type as ParameterSettingType] ?? undefined;
384
+ return parameter.transformation ?? ParameterTransformationDefaults[parameter.type as ParameterSettingType] ?? undefined;
385
385
  }