codify-plugin-lib 1.0.135 → 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.
@@ -25,6 +25,7 @@ export declare class ParsedResourceSettings<T extends StringIndexedObject> imple
25
25
  } | boolean;
26
26
  removeStatefulParametersBeforeDestroy?: boolean | undefined;
27
27
  dependencies?: string[] | undefined;
28
+ transformation?: InputTransformation;
28
29
  private settings;
29
30
  constructor(settings: ResourceSettings<T>);
30
31
  get typeId(): string;
@@ -7,6 +7,7 @@ export class ParsedResourceSettings {
7
7
  allowMultiple;
8
8
  removeStatefulParametersBeforeDestroy;
9
9
  dependencies;
10
+ transformation;
10
11
  settings;
11
12
  constructor(settings) {
12
13
  this.settings = settings;
@@ -15,6 +16,7 @@ export class ParsedResourceSettings {
15
16
  this.allowMultiple = settings.allowMultiple;
16
17
  this.removeStatefulParametersBeforeDestroy = settings.removeStatefulParametersBeforeDestroy;
17
18
  this.dependencies = settings.dependencies;
19
+ this.transformation = settings.transformation;
18
20
  this.validateSettings();
19
21
  }
20
22
  get typeId() {
@@ -250,6 +250,13 @@ ${JSON.stringify(refresh, null, 2)}
250
250
  ? await inputTransformation.from(config[key])
251
251
  : await inputTransformation.to(config[key]);
252
252
  }
253
+ if (this.settings.transformation) {
254
+ const transformed = reverse
255
+ ? await this.settings.transformation.from({ ...config })
256
+ : await this.settings.transformation.to({ ...config });
257
+ Object.keys(config).forEach((k) => delete config[k]);
258
+ Object.assign(config, transformed);
259
+ }
253
260
  }
254
261
  addDefaultValues(config) {
255
262
  if (!config) {
@@ -60,6 +60,13 @@ export interface ResourceSettings<T extends StringIndexedObject> {
60
60
  * and applying any input transformations. Use parameter settings to define stateful parameters as well.
61
61
  */
62
62
  parameterSettings?: Partial<Record<keyof T, ParameterSetting>>;
63
+ /**
64
+ * A config level transformation that is only applied to the user supplied desired config. This transformation is allowed
65
+ * to add, remove or modify keys as well as values. Changing this transformation for existing libraries will mess up existing states.
66
+ *
67
+ * @param desired
68
+ */
69
+ transformation?: InputTransformation;
63
70
  /**
64
71
  * Customize the import and destory behavior of the resource. By default, <code>codify import</code> and <code>codify destroy</code> will call
65
72
  * `refresh()` with every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
@@ -144,7 +151,7 @@ export interface DefaultParameterSetting {
144
151
  *
145
152
  * @param input The original parameter value from the desired config.
146
153
  */
147
- inputTransformation?: InputTransformation;
154
+ transformation?: InputTransformation;
148
155
  /**
149
156
  * Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
150
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.135",
3
+ "version": "1.0.136",
4
4
  "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -44,6 +44,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
44
44
 
45
45
  removeStatefulParametersBeforeDestroy?: boolean | undefined;
46
46
  dependencies?: string[] | undefined;
47
+ transformation?: InputTransformation;
47
48
  private settings: ResourceSettings<T>;
48
49
 
49
50
  constructor(settings: ResourceSettings<T>) {
@@ -53,6 +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;
57
+ this.transformation = settings.transformation;
56
58
 
57
59
  this.validateSettings();
58
60
  }
@@ -20,7 +20,7 @@ describe('Resource tests', () => {
20
20
  id: 'type',
21
21
  dependencies: ['homebrew', 'python'],
22
22
  parameterSettings: {
23
- propA: { canModify: true, inputTransformation: { to: (input) => untildify(input), from: (input) => tildify(input) } },
23
+ propA: { canModify: true, transformation: { to: (input) => untildify(input), from: (input) => tildify(input) } },
24
24
  },
25
25
  }
26
26
  }
@@ -543,7 +543,7 @@ describe('Resource tests', () => {
543
543
  parameterSettings: {
544
544
  propD: {
545
545
  type: 'array',
546
- inputTransformation: {
546
+ transformation: {
547
547
  to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
548
548
  Object.entries(h)
549
549
  .map(([k, v]) => [
@@ -618,4 +618,94 @@ describe('Resource tests', () => {
618
618
  }
619
619
  })
620
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
+ })
621
711
  });
@@ -350,6 +350,15 @@ ${JSON.stringify(refresh, null, 2)}
350
350
  ? await inputTransformation.from(config[key])
351
351
  : await inputTransformation.to(config[key]);
352
352
  }
353
+
354
+ if (this.settings.transformation) {
355
+ const transformed = reverse
356
+ ? await this.settings.transformation.from({ ...config })
357
+ : await this.settings.transformation.to({ ...config })
358
+
359
+ Object.keys(config).forEach((k) => delete config[k])
360
+ Object.assign(config, transformed);
361
+ }
353
362
  }
354
363
 
355
364
  private addDefaultValues(config: Partial<T> | null): void {
@@ -564,6 +564,86 @@ describe('Resource parameter tests', () => {
564
564
  expect(timestampC).to.be.lessThan(timestampA as any);
565
565
  })
566
566
 
567
+ it('Supports transform parameters', async () => {
568
+ const resource = spy(new class extends TestResource {
569
+ getSettings(): ResourceSettings<TestConfig> {
570
+ return {
571
+ id: 'resourceType',
572
+ transformation: {
573
+ to: (desired) => ({
574
+ propA: 'propA',
575
+ propB: 10,
576
+ }),
577
+ from: (current) => ({
578
+ propA: 'propA',
579
+ propB: 10,
580
+ })
581
+ }
582
+ }
583
+ }
584
+
585
+ async refresh(): Promise<Partial<TestConfig> | null> {
586
+ return {
587
+ propA: 'propA',
588
+ propB: 10,
589
+ }
590
+ }
591
+ });
592
+
593
+ const controller = new ResourceController(resource);
594
+ const plan = await controller.plan({ type: 'resourceType' }, { propC: 'abc' } as any, null, false);
595
+
596
+ expect(resource.refresh.called).to.be.true;
597
+ expect(resource.refresh.getCall(0).firstArg['propA']).to.exist;
598
+ expect(resource.refresh.getCall(0).firstArg['propB']).to.exist;
599
+ expect(resource.refresh.getCall(0).firstArg['propC']).to.not.exist;
600
+
601
+ expect(plan.desiredConfig?.propA).to.eq('propA');
602
+ expect(plan.desiredConfig?.propB).to.eq(10);
603
+ expect(plan.desiredConfig?.propC).to.be.undefined;
604
+
605
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
606
+ })
607
+
608
+ it('Supports transform parameters for state parameters', async () => {
609
+ const resource = spy(new class extends TestResource {
610
+ getSettings(): ResourceSettings<TestConfig> {
611
+ return {
612
+ id: 'resourceType',
613
+ transformation: {
614
+ to: (desired) => ({
615
+ propA: 'propA',
616
+ propB: 10,
617
+ }),
618
+ from: (desired) => ({
619
+ propA: 'propA',
620
+ propB: 10,
621
+ })
622
+ }
623
+ }
624
+ }
625
+
626
+ async refresh(): Promise<Partial<TestConfig> | null> {
627
+ return {
628
+ propA: 'propA',
629
+ propB: 10,
630
+ }
631
+ }
632
+ });
633
+
634
+ const controller = new ResourceController(resource);
635
+ const plan = await controller.plan({ type: 'resourceType' }, null, { propC: 'abc' }, true);
636
+
637
+ expect(resource.refresh.called).to.be.true;
638
+ expect(resource.refresh.getCall(0).firstArg['propA']).to.exist;
639
+ expect(resource.refresh.getCall(0).firstArg['propB']).to.exist;
640
+ expect(resource.refresh.getCall(0).firstArg['propC']).to.not.exist;
641
+
642
+ expect(plan.currentConfig?.propA).to.eq('propA');
643
+ expect(plan.currentConfig?.propB).to.eq(10);
644
+ expect(plan.currentConfig?.propC).to.be.undefined;
645
+ })
646
+
567
647
  it('Allows import required parameters customization', () => {
568
648
  const resource = new class extends TestResource {
569
649
  getSettings(): ResourceSettings<TestConfig> {
@@ -784,7 +864,7 @@ describe('Resource parameter tests', () => {
784
864
  parameterSettings: {
785
865
  propD: {
786
866
  type: 'array',
787
- inputTransformation: {
867
+ transformation: {
788
868
  to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
789
869
  Object.entries(h)
790
870
  .map(([k, v]) => [
@@ -850,7 +930,7 @@ describe('Resource parameter tests', () => {
850
930
  getSettings(): any {
851
931
  return {
852
932
  type: 'array',
853
- inputTransformation: {
933
+ transformation: {
854
934
  to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
855
935
  Object.entries(h)
856
936
  .map(([k, v]) => [
@@ -75,6 +75,14 @@ export interface ResourceSettings<T extends StringIndexedObject> {
75
75
  */
76
76
  parameterSettings?: Partial<Record<keyof T, ParameterSetting>>;
77
77
 
78
+ /**
79
+ * A config level transformation that is only applied to the user supplied desired config. This transformation is allowed
80
+ * to add, remove or modify keys as well as values. Changing this transformation for existing libraries will mess up existing states.
81
+ *
82
+ * @param desired
83
+ */
84
+ transformation?: InputTransformation;
85
+
78
86
  /**
79
87
  * Customize the import and destory behavior of the resource. By default, <code>codify import</code> and <code>codify destroy</code> will call
80
88
  * `refresh()` with every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
@@ -181,7 +189,7 @@ export interface DefaultParameterSetting {
181
189
  *
182
190
  * @param input The original parameter value from the desired config.
183
191
  */
184
- inputTransformation?: InputTransformation;
192
+ transformation?: InputTransformation;
185
193
 
186
194
  /**
187
195
  * Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
@@ -346,10 +354,10 @@ export function resolveParameterTransformFn(
346
354
  parameter: ParameterSetting
347
355
  ): InputTransformation | undefined {
348
356
 
349
- if (parameter.type === 'stateful' && !parameter.inputTransformation) {
357
+ if (parameter.type === 'stateful' && !parameter.transformation) {
350
358
  const sp = (parameter as StatefulParameterSetting).definition.getSettings();
351
- if (sp.inputTransformation) {
352
- return (parameter as StatefulParameterSetting).definition?.getSettings()?.inputTransformation
359
+ if (sp.transformation) {
360
+ return (parameter as StatefulParameterSetting).definition?.getSettings()?.transformation
353
361
  }
354
362
 
355
363
  return sp.type ? ParameterTransformationDefaults[sp.type] : undefined;
@@ -358,7 +366,7 @@ export function resolveParameterTransformFn(
358
366
  if (parameter.type === 'array'
359
367
  && (parameter as ArrayParameterSetting).itemType
360
368
  && ParameterTransformationDefaults[(parameter as ArrayParameterSetting).itemType!]
361
- && !parameter.inputTransformation
369
+ && !parameter.transformation
362
370
  ) {
363
371
  const itemType = (parameter as ArrayParameterSetting).itemType!;
364
372
  const itemTransformation = ParameterTransformationDefaults[itemType]!;
@@ -373,5 +381,5 @@ export function resolveParameterTransformFn(
373
381
  }
374
382
  }
375
383
 
376
- return parameter.inputTransformation ?? ParameterTransformationDefaults[parameter.type as ParameterSettingType] ?? undefined;
384
+ return parameter.transformation ?? ParameterTransformationDefaults[parameter.type as ParameterSettingType] ?? undefined;
377
385
  }