@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.
Files changed (152) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.json +30 -0
  3. package/.github/workflows/release.yaml +19 -0
  4. package/.github/workflows/unit-test-ci.yaml +18 -0
  5. package/.prettierrc.json +1 -0
  6. package/bin/build.js +189 -0
  7. package/dist/bin/build.d.ts +1 -0
  8. package/dist/bin/build.js +80 -0
  9. package/dist/bin/deploy-plugin.d.ts +2 -0
  10. package/dist/bin/deploy-plugin.js +8 -0
  11. package/dist/common/errors.d.ts +8 -0
  12. package/dist/common/errors.js +24 -0
  13. package/dist/entities/change-set.d.ts +24 -0
  14. package/dist/entities/change-set.js +152 -0
  15. package/dist/entities/errors.d.ts +4 -0
  16. package/dist/entities/errors.js +7 -0
  17. package/dist/entities/plan-types.d.ts +25 -0
  18. package/dist/entities/plan-types.js +1 -0
  19. package/dist/entities/plan.d.ts +15 -0
  20. package/dist/entities/plan.js +127 -0
  21. package/dist/entities/plugin.d.ts +16 -0
  22. package/dist/entities/plugin.js +80 -0
  23. package/dist/entities/resource-options.d.ts +31 -0
  24. package/dist/entities/resource-options.js +76 -0
  25. package/dist/entities/resource-types.d.ts +11 -0
  26. package/dist/entities/resource-types.js +1 -0
  27. package/dist/entities/resource.d.ts +42 -0
  28. package/dist/entities/resource.js +303 -0
  29. package/dist/entities/stateful-parameter.d.ts +29 -0
  30. package/dist/entities/stateful-parameter.js +46 -0
  31. package/dist/entities/transform-parameter.d.ts +4 -0
  32. package/dist/entities/transform-parameter.js +2 -0
  33. package/dist/errors.d.ts +4 -0
  34. package/dist/errors.js +7 -0
  35. package/dist/index.d.ts +20 -0
  36. package/dist/index.js +26 -0
  37. package/dist/messages/handlers.d.ts +14 -0
  38. package/dist/messages/handlers.js +134 -0
  39. package/dist/messages/sender.d.ts +11 -0
  40. package/dist/messages/sender.js +57 -0
  41. package/dist/plan/change-set.d.ts +53 -0
  42. package/dist/plan/change-set.js +153 -0
  43. package/dist/plan/plan-types.d.ts +23 -0
  44. package/dist/plan/plan-types.js +1 -0
  45. package/dist/plan/plan.d.ts +66 -0
  46. package/dist/plan/plan.js +328 -0
  47. package/dist/plugin/plugin.d.ts +24 -0
  48. package/dist/plugin/plugin.js +200 -0
  49. package/dist/pty/background-pty.d.ts +21 -0
  50. package/dist/pty/background-pty.js +127 -0
  51. package/dist/pty/index.d.ts +50 -0
  52. package/dist/pty/index.js +20 -0
  53. package/dist/pty/promise-queue.d.ts +5 -0
  54. package/dist/pty/promise-queue.js +26 -0
  55. package/dist/pty/seqeuntial-pty.d.ts +17 -0
  56. package/dist/pty/seqeuntial-pty.js +119 -0
  57. package/dist/pty/vitest.config.d.ts +2 -0
  58. package/dist/pty/vitest.config.js +11 -0
  59. package/dist/resource/config-parser.d.ts +11 -0
  60. package/dist/resource/config-parser.js +21 -0
  61. package/dist/resource/parsed-resource-settings.d.ts +47 -0
  62. package/dist/resource/parsed-resource-settings.js +196 -0
  63. package/dist/resource/resource-controller.d.ts +36 -0
  64. package/dist/resource/resource-controller.js +402 -0
  65. package/dist/resource/resource-settings.d.ts +303 -0
  66. package/dist/resource/resource-settings.js +147 -0
  67. package/dist/resource/resource.d.ts +144 -0
  68. package/dist/resource/resource.js +44 -0
  69. package/dist/resource/stateful-parameter.d.ts +165 -0
  70. package/dist/resource/stateful-parameter.js +94 -0
  71. package/dist/scripts/deploy.d.ts +1 -0
  72. package/dist/scripts/deploy.js +2 -0
  73. package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
  74. package/dist/stateful-parameter/stateful-parameter-controller.js +81 -0
  75. package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
  76. package/dist/stateful-parameter/stateful-parameter.js +43 -0
  77. package/dist/test.d.ts +1 -0
  78. package/dist/test.js +5 -0
  79. package/dist/utils/codify-spawn.d.ts +29 -0
  80. package/dist/utils/codify-spawn.js +136 -0
  81. package/dist/utils/debug.d.ts +2 -0
  82. package/dist/utils/debug.js +10 -0
  83. package/dist/utils/file-utils.d.ts +23 -0
  84. package/dist/utils/file-utils.js +186 -0
  85. package/dist/utils/functions.d.ts +12 -0
  86. package/dist/utils/functions.js +74 -0
  87. package/dist/utils/index.d.ts +46 -0
  88. package/dist/utils/index.js +271 -0
  89. package/dist/utils/internal-utils.d.ts +12 -0
  90. package/dist/utils/internal-utils.js +74 -0
  91. package/dist/utils/load-resources.d.ts +1 -0
  92. package/dist/utils/load-resources.js +46 -0
  93. package/dist/utils/package-json-utils.d.ts +12 -0
  94. package/dist/utils/package-json-utils.js +34 -0
  95. package/dist/utils/pty-local-storage.d.ts +2 -0
  96. package/dist/utils/pty-local-storage.js +2 -0
  97. package/dist/utils/spawn-2.d.ts +5 -0
  98. package/dist/utils/spawn-2.js +7 -0
  99. package/dist/utils/spawn.d.ts +29 -0
  100. package/dist/utils/spawn.js +124 -0
  101. package/dist/utils/utils.d.ts +18 -0
  102. package/dist/utils/utils.js +86 -0
  103. package/dist/utils/verbosity-level.d.ts +5 -0
  104. package/dist/utils/verbosity-level.js +9 -0
  105. package/package.json +59 -0
  106. package/rollup.config.js +24 -0
  107. package/src/common/errors.test.ts +43 -0
  108. package/src/common/errors.ts +31 -0
  109. package/src/errors.ts +8 -0
  110. package/src/index.test.ts +6 -0
  111. package/src/index.ts +30 -0
  112. package/src/messages/handlers.test.ts +329 -0
  113. package/src/messages/handlers.ts +181 -0
  114. package/src/messages/sender.ts +69 -0
  115. package/src/plan/change-set.test.ts +280 -0
  116. package/src/plan/change-set.ts +236 -0
  117. package/src/plan/plan-types.ts +27 -0
  118. package/src/plan/plan.test.ts +413 -0
  119. package/src/plan/plan.ts +499 -0
  120. package/src/plugin/plugin.test.ts +533 -0
  121. package/src/plugin/plugin.ts +291 -0
  122. package/src/pty/background-pty.test.ts +69 -0
  123. package/src/pty/background-pty.ts +154 -0
  124. package/src/pty/index.test.ts +129 -0
  125. package/src/pty/index.ts +66 -0
  126. package/src/pty/promise-queue.ts +33 -0
  127. package/src/pty/seqeuntial-pty.ts +151 -0
  128. package/src/pty/sequential-pty.test.ts +194 -0
  129. package/src/resource/config-parser.ts +42 -0
  130. package/src/resource/parsed-resource-settings.test.ts +186 -0
  131. package/src/resource/parsed-resource-settings.ts +307 -0
  132. package/src/resource/resource-controller-stateful-mode.test.ts +253 -0
  133. package/src/resource/resource-controller.test.ts +1081 -0
  134. package/src/resource/resource-controller.ts +563 -0
  135. package/src/resource/resource-settings.test.ts +1213 -0
  136. package/src/resource/resource-settings.ts +545 -0
  137. package/src/resource/resource.ts +157 -0
  138. package/src/stateful-parameter/stateful-parameter-controller.test.ts +244 -0
  139. package/src/stateful-parameter/stateful-parameter-controller.ts +111 -0
  140. package/src/stateful-parameter/stateful-parameter.ts +160 -0
  141. package/src/utils/debug.ts +11 -0
  142. package/src/utils/file-utils.test.ts +7 -0
  143. package/src/utils/file-utils.ts +231 -0
  144. package/src/utils/functions.ts +103 -0
  145. package/src/utils/index.ts +340 -0
  146. package/src/utils/internal-utils.test.ts +52 -0
  147. package/src/utils/pty-local-storage.ts +3 -0
  148. package/src/utils/test-utils.test.ts +96 -0
  149. package/src/utils/verbosity-level.ts +11 -0
  150. package/tsconfig.json +26 -0
  151. package/tsconfig.test.json +9 -0
  152. package/vitest.config.ts +10 -0
@@ -0,0 +1,307 @@
1
+ import { JSONSchemaType } from 'ajv';
2
+ import { LinuxDistro, OS, StringIndexedObject } from 'codify-schemas';
3
+ import { ZodObject, z } from 'zod';
4
+
5
+ import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
6
+ import {
7
+ ArrayParameterSetting,
8
+ DefaultParameterSetting,
9
+ InputTransformation,
10
+ ParameterSetting,
11
+ ResourceSettings,
12
+ StatefulParameterSetting,
13
+ resolveElementEqualsFn,
14
+ resolveEqualsFn,
15
+ resolveMatcher,
16
+ resolveParameterTransformFn
17
+ } from './resource-settings.js';
18
+
19
+ export interface ParsedStatefulParameterSetting extends DefaultParameterSetting {
20
+ type: 'stateful',
21
+ controller: StatefulParameterController<any, unknown>
22
+ order?: number,
23
+ nestedSettings: ParsedParameterSetting;
24
+ }
25
+
26
+ export type ParsedArrayParameterSetting = {
27
+ isElementEqual: (a: unknown, b: unknown) => boolean;
28
+ isEqual: (a: unknown, b: unknown) => boolean;
29
+ } & ArrayParameterSetting
30
+
31
+ export type ParsedParameterSetting =
32
+ {
33
+ isEqual: (desired: unknown, current: unknown) => boolean;
34
+ } & (DefaultParameterSetting
35
+ | ParsedArrayParameterSetting
36
+ | ParsedStatefulParameterSetting)
37
+
38
+ export class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
39
+ private cache = new Map<string, unknown>();
40
+ id!: string;
41
+ description?: string;
42
+
43
+ schema?: Partial<JSONSchemaType<T | any>>;
44
+ allowMultiple?: {
45
+ identifyingParameters?: string[];
46
+ matcher?: (desired: Partial<T>, current: Partial<T>) => boolean;
47
+ findAllParameters?: () => Promise<Array<Partial<T>>>
48
+ } | boolean;
49
+
50
+ removeStatefulParametersBeforeDestroy?: boolean | undefined;
51
+ dependencies?: string[] | undefined;
52
+ transformation?: InputTransformation;
53
+
54
+ operatingSystems!: Array<OS>;
55
+ linuxDistros?: Array<LinuxDistro>;
56
+
57
+ isSensitive?: boolean;
58
+
59
+ private settings: ResourceSettings<T>;
60
+
61
+ constructor(settings: ResourceSettings<T>) {
62
+ this.settings = settings;
63
+ const { parameterSettings, schema, ...rest } = settings;
64
+
65
+ Object.assign(this, rest);
66
+
67
+ if (schema) {
68
+ this.schema = schema instanceof ZodObject
69
+ ? z.toJSONSchema(schema.strict(), {
70
+ target: 'draft-7',
71
+ override(ctx) {
72
+ ctx.jsonSchema.title = settings.id;
73
+ ctx.jsonSchema.description = settings.description ?? `${settings.id} resource. Can be used to manage ${settings.id}`;
74
+ }
75
+ }) as JSONSchemaType<T>
76
+ : schema;
77
+ }
78
+
79
+ this.validateSettings();
80
+ }
81
+
82
+ get typeId(): string {
83
+ return this.id;
84
+ }
85
+
86
+ get canDestroy(): boolean {
87
+ return !this.settings.importAndDestroy?.preventDestroy;
88
+ }
89
+
90
+ get statefulParameters(): Map<keyof T, StatefulParameterController<T, T[keyof T]>> {
91
+ return this.getFromCacheOrCreate('statefulParameters', () => {
92
+
93
+ const statefulParameters = Object.entries(this.settings.parameterSettings ?? {})
94
+ .filter(([, p]) => p?.type === 'stateful')
95
+ .map(([k, v]) => [
96
+ k,
97
+ new StatefulParameterController((v as StatefulParameterSetting).definition)
98
+ ] as const)
99
+
100
+ return new Map(statefulParameters) as Map<keyof T, StatefulParameterController<T, T[keyof T]>>;
101
+ })
102
+ }
103
+
104
+ get parameterSettings(): Record<keyof T, ParsedParameterSetting> {
105
+ return this.getFromCacheOrCreate('parameterSetting', () => {
106
+
107
+ const settings = Object.entries(this.settings.parameterSettings ?? {})
108
+ .map(([k, v]) => [k, v!] as const)
109
+ .map(([k, v]) => {
110
+ v.isEqual = resolveEqualsFn(v);
111
+
112
+ if (v.type === 'stateful') {
113
+ const spController = this.statefulParameters.get(k);
114
+ const parsed = {
115
+ ...v,
116
+ controller: spController,
117
+ nestedSettings: spController?.parsedSettings,
118
+ };
119
+
120
+ return [k, parsed as ParsedStatefulParameterSetting];
121
+ }
122
+
123
+ if (v.type === 'array') {
124
+ const parsed = {
125
+ ...v,
126
+ isElementEqual: resolveElementEqualsFn(v as ArrayParameterSetting)
127
+ }
128
+
129
+ return [k, parsed as ParsedArrayParameterSetting];
130
+ }
131
+
132
+ return [k, v];
133
+ })
134
+
135
+ return Object.fromEntries(settings);
136
+ });
137
+ }
138
+
139
+ get defaultValues(): Partial<Record<keyof T, unknown>> {
140
+ return this.getFromCacheOrCreate('defaultValues', () => {
141
+
142
+ if (!this.settings.parameterSettings) {
143
+ return {};
144
+ }
145
+
146
+ const defaultValues = Object.fromEntries(
147
+ Object.entries(this.settings.parameterSettings)
148
+ .filter(([, v]) => v!.default !== undefined)
149
+ .map(([k, v]) => [k, v!.default] as const)
150
+ )
151
+
152
+ const statefulParameterDefaultValues = Object.fromEntries(
153
+ Object.entries(this.settings.parameterSettings)
154
+ .filter(([, v]) => v?.type === 'stateful')
155
+ .filter(([, v]) => (v as StatefulParameterSetting).definition.getSettings().default !== undefined)
156
+ .map(([k, v]) => [k, (v as StatefulParameterSetting).definition.getSettings().default] as const)
157
+ )
158
+
159
+ return { ...defaultValues, ...statefulParameterDefaultValues } as Partial<Record<keyof T, unknown>>;
160
+ });
161
+ }
162
+
163
+ get inputTransformations(): Partial<Record<keyof T, InputTransformation>> {
164
+ return this.getFromCacheOrCreate('inputTransformations', () => {
165
+ if (!this.settings.parameterSettings) {
166
+ return {};
167
+ }
168
+
169
+ return Object.fromEntries(
170
+ Object.entries(this.settings.parameterSettings)
171
+ .filter(([_, v]) => resolveParameterTransformFn(v!) !== undefined)
172
+ .map(([k, v]) => [k, resolveParameterTransformFn(v!)] as const)
173
+ ) as Record<keyof T, InputTransformation>;
174
+ });
175
+ }
176
+
177
+ get statefulParameterOrder(): Map<keyof T, number> {
178
+ return this.getFromCacheOrCreate('stateParameterOrder', () => {
179
+
180
+ const entries = Object.entries(this.settings.parameterSettings ?? {})
181
+ .filter(([, v]) => v?.type === 'stateful')
182
+ .map(([k, v]) => [k, v as StatefulParameterSetting] as const)
183
+
184
+ const orderedEntries = entries.filter(([, v]) => v.order !== undefined)
185
+ const unorderedEntries = entries.filter(([, v]) => v.order === undefined)
186
+
187
+ orderedEntries.sort((a, b) => a[1].order! - b[1].order!);
188
+
189
+ const resultArray = [
190
+ ...orderedEntries.map(([k]) => k),
191
+ ...unorderedEntries.map(([k]) => k)
192
+ ]
193
+
194
+ return new Map(resultArray.map((key, idx) => [key, idx]));
195
+ });
196
+ }
197
+
198
+ get matcher(): (desired: Partial<T>, current: Partial<T>) => boolean {
199
+ return resolveMatcher(this);
200
+ }
201
+
202
+ private validateSettings(): void {
203
+ // validate parameter settings
204
+ if (this.settings.parameterSettings) {
205
+ for (const [k, v] of Object.entries(this.settings.parameterSettings)) {
206
+ if (!v) {
207
+ throw new Error(`Resource: ${this.id}. Parameter setting ${k} was left undefined`);
208
+ }
209
+
210
+ this.validateParameterEqualsFn(v, k);
211
+ }
212
+ }
213
+
214
+ if (Object.entries(this.parameterSettings).some(([k, v]) =>
215
+ v.type === 'stateful'
216
+ && typeof this.settings.allowMultiple === 'object' && this.settings.allowMultiple?.identifyingParameters?.includes(k))) {
217
+ throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed to be identifying parameters for allowMultiple.`)
218
+ }
219
+
220
+ const schema = this.schema as JSONSchemaType<any>;
221
+ if (!this.settings.importAndDestroy && (schema?.oneOf
222
+ && Array.isArray(schema.oneOf)
223
+ && schema.oneOf.some((s) => s.required)
224
+ )
225
+ || (schema?.anyOf
226
+ && Array.isArray(schema.anyOf)
227
+ && schema.anyOf.some((s) => s.required)
228
+ )
229
+ || (schema?.allOf
230
+ && Array.isArray(schema.allOf)
231
+ && schema.allOf.some((s) => s.required)
232
+ )
233
+ || (schema?.then
234
+ && Array.isArray(schema.then)
235
+ && schema.then.some((s) => s.required)
236
+ )
237
+ || (schema?.else
238
+ && Array.isArray(schema.else)
239
+ && schema.else.some((s) => s.required)
240
+ )
241
+ ) {
242
+ throw new Error(`In the schema of ${this.settings.id}, a conditional required was declared (within anyOf, allOf, oneOf, else, or then) but an` +
243
+ 'import.requiredParameters was not found in the resource settings. This is required because Codify uses the required parameter to' +
244
+ 'determine the prompt to ask users during imports. It can\'t parse which parameters are needed when ' +
245
+ 'required is declared conditionally.'
246
+ )
247
+ }
248
+
249
+ if (this.settings.importAndDestroy) {
250
+ const { requiredParameters, refreshKeys, defaultRefreshValues } = this.settings.importAndDestroy;
251
+
252
+ const requiredParametersNotInSchema = requiredParameters
253
+ ?.filter(
254
+ (p) => schema && !(schema.properties[p])
255
+ )
256
+ if (schema && requiredParametersNotInSchema && requiredParametersNotInSchema.length > 0) {
257
+ throw new Error(`The following properties were declared in settings.import.requiredParameters but were not found in the schema:
258
+ ${JSON.stringify(requiredParametersNotInSchema, null, 2)}`)
259
+ }
260
+
261
+ const refreshKeyNotInSchema = refreshKeys
262
+ ?.filter(
263
+ (k) => schema && !(schema.properties[k])
264
+ )
265
+ if (schema && refreshKeyNotInSchema && refreshKeyNotInSchema.length > 0) {
266
+ throw new Error(`The following properties were declared in settings.import.refreshKeys but were not found in the schema:
267
+ ${JSON.stringify(requiredParametersNotInSchema, null, 2)}`)
268
+ }
269
+
270
+ const refreshValueNotInRefreshKey =
271
+ Object.entries(defaultRefreshValues ?? {})
272
+ .filter(
273
+ ([k]) => schema && !(schema.properties[k])
274
+ ).map(([k]) => k)
275
+
276
+ if (schema && refreshValueNotInRefreshKey.length > 0) {
277
+ throw new Error(`Properties declared in defaultRefreshValues must already be declared in refreshKeys:
278
+ ${JSON.stringify(refreshValueNotInRefreshKey, null, 2)}`)
279
+ }
280
+ }
281
+ }
282
+
283
+ private validateParameterEqualsFn(parameter: ParameterSetting, key: string): void {
284
+ if (parameter.type === 'stateful') {
285
+ const nestedSettings = (parameter as StatefulParameterSetting).definition.getSettings();
286
+
287
+ if (nestedSettings.type === 'stateful') {
288
+ throw new Error(`Nested stateful parameters are not allowed for ${key}`);
289
+ }
290
+
291
+ this.validateParameterEqualsFn(nestedSettings, key);
292
+ }
293
+
294
+ // The rest of the types have defaults set already
295
+ }
296
+
297
+ private getFromCacheOrCreate<T2>(key: string, create: () => T2): T2 {
298
+ if (this.cache.has(key)) {
299
+ return this.cache.get(key) as T2
300
+ }
301
+
302
+ const result = create();
303
+
304
+ this.cache.set(key, result)
305
+ return result;
306
+ }
307
+ }
@@ -0,0 +1,253 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { OS, ParameterOperation, ResourceOperation } from 'codify-schemas';
3
+ import { TestConfig, TestResource, TestStatefulParameter } from '../utils/test-utils.test.js';
4
+ import { ResourceSettings } from './resource-settings.js';
5
+ import { ResourceController } from './resource-controller.js';
6
+
7
+
8
+ describe('Resource tests for stateful plans', () => {
9
+ it('Supports delete operations ', async () => {
10
+ const resource = new class extends TestResource {
11
+ async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
12
+ return {
13
+ propA: 'propADifferent',
14
+ propB: undefined,
15
+ propC: 'propCDifferent',
16
+ }
17
+ }
18
+ }
19
+
20
+ const controller = new ResourceController(resource);
21
+ const plan = await controller.plan(
22
+ { type: 'type' },
23
+ null,
24
+ {
25
+ propA: 'propA',
26
+ propB: 10,
27
+ propC: 'propC',
28
+ }, true
29
+ );
30
+
31
+ expect(plan).toMatchObject({
32
+ changeSet: {
33
+ operation: ResourceOperation.DESTROY,
34
+ parameterChanges: [
35
+ {
36
+ name: "propA",
37
+ previousValue: "propADifferent",
38
+ newValue: null,
39
+ operation: ParameterOperation.REMOVE
40
+ },
41
+ {
42
+ name: 'propB',
43
+ previousValue: null,
44
+ newValue: null,
45
+ operation: ParameterOperation.REMOVE
46
+ },
47
+ {
48
+ name: "propC",
49
+ previousValue: "propCDifferent",
50
+ newValue: null,
51
+ operation: ParameterOperation.REMOVE
52
+ },
53
+ ]
54
+ },
55
+ coreParameters: {
56
+ type: 'type'
57
+ }
58
+ })
59
+ })
60
+
61
+ it('Supports create operations', async () => {
62
+ const resource = new class extends TestResource {
63
+ async refresh(): Promise<Partial<TestConfig> | null> {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ const controller = new ResourceController(resource);
69
+ const plan = await controller.plan(
70
+ { type: 'resource' },
71
+ {
72
+ propA: 'propA',
73
+ propB: 10,
74
+ propC: 'propC',
75
+ },
76
+ null,
77
+ true
78
+ );
79
+
80
+ expect(plan).toMatchObject({
81
+ changeSet: {
82
+ operation: ResourceOperation.CREATE,
83
+ parameterChanges: [
84
+ {
85
+ name: "propA",
86
+ newValue: "propA",
87
+ previousValue: null,
88
+ operation: ParameterOperation.ADD
89
+ },
90
+ {
91
+ name: "propB",
92
+ newValue: 10,
93
+ previousValue: null,
94
+ operation: ParameterOperation.ADD
95
+ },
96
+ {
97
+ name: "propC",
98
+ newValue: 'propC',
99
+ previousValue: null,
100
+ operation: ParameterOperation.ADD
101
+ },
102
+ ]
103
+ },
104
+ coreParameters: {
105
+ type: 'resource'
106
+ }
107
+ })
108
+ })
109
+
110
+ it('Supports re-create operations', async () => {
111
+ const resource = new class extends TestResource {
112
+ async refresh(): Promise<Partial<TestConfig> | null> {
113
+ return {
114
+ propA: 'propA',
115
+ propC: 'propC',
116
+ };
117
+ }
118
+ }
119
+
120
+ const controller = new ResourceController(resource)
121
+ const plan = await controller.plan(
122
+ { type: 'type' },
123
+ {
124
+ type: 'type',
125
+ propA: 'propA',
126
+ propB: 10,
127
+ propC: 'propC',
128
+ },
129
+ {
130
+ type: 'type',
131
+ propA: 'propA',
132
+ propC: 'propC'
133
+ },
134
+ true
135
+ );
136
+
137
+ expect(plan).toMatchObject({
138
+ changeSet: {
139
+ operation: ResourceOperation.RECREATE,
140
+ parameterChanges: expect.arrayContaining([
141
+ {
142
+ name: "propA",
143
+ newValue: "propA",
144
+ previousValue: "propA",
145
+ operation: ParameterOperation.NOOP,
146
+ isSensitive: false,
147
+
148
+ },
149
+ {
150
+ name: "propB",
151
+ newValue: 10,
152
+ previousValue: null,
153
+ operation: ParameterOperation.ADD,
154
+ isSensitive: false,
155
+ },
156
+ {
157
+ name: "propC",
158
+ newValue: 'propC',
159
+ previousValue: 'propC',
160
+ operation: ParameterOperation.NOOP,
161
+ isSensitive: false,
162
+ },
163
+ ])
164
+ },
165
+ coreParameters: {
166
+ type: 'type'
167
+ }
168
+ })
169
+ })
170
+
171
+ it('Supports stateful parameters', async () => {
172
+ const statefulParameter = new class extends TestStatefulParameter {
173
+ async refresh(): Promise<string | null> {
174
+ return null;
175
+ }
176
+ }
177
+
178
+ const resource = new class extends TestResource {
179
+ getSettings(): ResourceSettings<TestConfig> {
180
+ return {
181
+ id: 'type',
182
+ operatingSystems: [OS.Darwin],
183
+ parameterSettings: {
184
+ propD: { type: 'stateful', definition: statefulParameter },
185
+ }
186
+ };
187
+ }
188
+
189
+ async refresh(): Promise<Partial<TestConfig> | null> {
190
+ return {
191
+ propA: 'propA',
192
+ propC: 'propC',
193
+ propB: undefined
194
+ };
195
+ }
196
+ }
197
+
198
+ const controller = new ResourceController(resource);
199
+ const plan = await controller.plan(
200
+ { type: 'type' },
201
+ {
202
+ propA: 'propA',
203
+ propB: 10,
204
+ propC: 'propC',
205
+ propD: 'propD'
206
+ },
207
+ {
208
+ propA: 'propA',
209
+ propC: 'propC'
210
+ },
211
+ true
212
+ );
213
+
214
+ expect(plan).toMatchObject({
215
+ changeSet: {
216
+ operation: ResourceOperation.RECREATE,
217
+ parameterChanges: expect.arrayContaining([
218
+ {
219
+ name: "propA",
220
+ newValue: "propA",
221
+ previousValue: "propA",
222
+ operation: ParameterOperation.NOOP,
223
+ isSensitive: false,
224
+ },
225
+ {
226
+ name: "propB",
227
+ newValue: 10,
228
+ previousValue: null,
229
+ operation: ParameterOperation.ADD,
230
+ isSensitive: false,
231
+ },
232
+ {
233
+ name: "propC",
234
+ newValue: 'propC',
235
+ previousValue: 'propC',
236
+ operation: ParameterOperation.NOOP,
237
+ isSensitive: false,
238
+ },
239
+ {
240
+ name: "propD",
241
+ newValue: 'propD',
242
+ previousValue: null,
243
+ operation: ParameterOperation.ADD,
244
+ isSensitive: false,
245
+ },
246
+ ])
247
+ },
248
+ coreParameters: {
249
+ type: 'type'
250
+ }
251
+ })
252
+ })
253
+ })