@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,244 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { spy } from 'sinon';
3
+ import { ParameterOperation, ResourceOperation } from 'codify-schemas';
4
+ import {
5
+ TestArrayStatefulParameter,
6
+ TestConfig,
7
+ testPlan,
8
+ TestResource,
9
+ TestStatefulParameter
10
+ } from '../utils/test-utils.test.js';
11
+ import { ArrayParameterSetting, ParameterSetting, ResourceSettings } from '../resource/resource-settings.js';
12
+ import { ResourceController } from '../resource/resource-controller.js';
13
+ import { StatefulParameterController } from './stateful-parameter-controller.js';
14
+
15
+ describe('Stateful parameter tests', () => {
16
+ it('addItem is called the correct number of times', async () => {
17
+ const plan = testPlan<TestConfig>({
18
+ desired: { propZ: ['a', 'b', 'c'] },
19
+ });
20
+
21
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.CREATE);
22
+ expect(plan.changeSet.parameterChanges.length).to.eq(1);
23
+
24
+ const parameter = spy(new TestArrayStatefulParameter());
25
+ const controller = new StatefulParameterController(parameter);
26
+ await controller.add((plan.desiredConfig! as any).propZ, plan);
27
+
28
+ expect(parameter.addItem.callCount).to.eq(3);
29
+ expect(parameter.removeItem.called).to.be.false;
30
+ })
31
+
32
+ it('applyRemoveItem is called the correct number of times', async () => {
33
+ const plan = testPlan<TestConfig>({
34
+ desired: null,
35
+ current: [{ propZ: ['a', 'b', 'c'] }],
36
+ state: { propZ: ['a', 'b', 'c'] },
37
+ isStateful: true,
38
+ });
39
+
40
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.DESTROY);
41
+ expect(plan.changeSet.parameterChanges.length).to.eq(1);
42
+
43
+ const parameter = spy(new TestArrayStatefulParameter());
44
+ const controller = new StatefulParameterController(parameter);
45
+ await controller.remove((plan.currentConfig as any).propZ, plan);
46
+
47
+ expect(parameter.addItem.called).to.be.false;
48
+ expect(parameter.removeItem.callCount).to.eq(3);
49
+ })
50
+
51
+ it('In stateless mode only applyAddItem is called only for modifies', async () => {
52
+ const parameter = new TestArrayStatefulParameter()
53
+ const plan = testPlan<TestConfig>({
54
+ desired: { propZ: ['a', 'c', 'd', 'e', 'f'] }, // b to remove, d, e, f to add
55
+ current: [{ propZ: ['a', 'b', 'c'] }],
56
+ settings: { id: 'type', parameterSettings: { propZ: { type: 'stateful', definition: parameter } } },
57
+ });
58
+
59
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.MODIFY);
60
+ expect(plan.changeSet.parameterChanges[0]).toMatchObject({
61
+ name: 'propZ',
62
+ previousValue: ['c', 'a'], // In stateless mode the previous value gets filtered to prevent deletes
63
+ newValue: ['a', 'c', 'd', 'e', 'f'],
64
+ operation: ParameterOperation.MODIFY,
65
+ })
66
+
67
+
68
+ const testParameter = spy(parameter);
69
+ const controller = new StatefulParameterController(testParameter);
70
+ await controller.modify((plan.desiredConfig as any).propZ, (plan.currentConfig as any).propZ, plan);
71
+
72
+ expect(testParameter.addItem.calledThrice).to.be.true;
73
+ expect(testParameter.removeItem.called).to.be.false;
74
+ })
75
+
76
+ it('isElementEqual is called for modifies', async () => {
77
+ const testParameter = spy(new class extends TestArrayStatefulParameter {
78
+ getSettings(): ArrayParameterSetting {
79
+ return {
80
+ type: 'array',
81
+ isElementEqual: (desired, current) => current.includes(desired),
82
+ }
83
+ }
84
+ });
85
+
86
+ const plan = testPlan<TestConfig>({
87
+ desired: { propZ: ['9.12', '9.13'] }, // b to remove, d, e, f to add
88
+ current: [{ propZ: ['9.12.9'] }],
89
+ settings: { id: 'type', parameterSettings: { propZ: { type: 'stateful', definition: testParameter } } }
90
+ });
91
+
92
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.MODIFY);
93
+ expect(plan.changeSet.parameterChanges[0]).toMatchObject({
94
+ name: 'propZ',
95
+ previousValue: ['9.12.9'],
96
+ newValue: ['9.12', '9.13'],
97
+ operation: ParameterOperation.MODIFY,
98
+ })
99
+
100
+ const controller = new StatefulParameterController(testParameter);
101
+ await controller.modify((plan.desiredConfig as any).propZ, (plan.currentConfig as any).propZ, plan);
102
+
103
+ expect(testParameter.addItem.calledOnce).to.be.true;
104
+ expect(testParameter.removeItem.called).to.be.false;
105
+ })
106
+
107
+ it('isEqual works with type defaults', () => {
108
+ const testParameter = spy(new class extends TestStatefulParameter {
109
+ getSettings(): ParameterSetting {
110
+ return {
111
+ type: 'version',
112
+ }
113
+ }
114
+ });
115
+
116
+ const plan = testPlan<TestConfig>({
117
+ desired: { propZ: '20' },
118
+ current: [{ propZ: '20.17.0' }],
119
+ settings: { id: 'type', parameterSettings: { propZ: { type: 'stateful', definition: testParameter } } }
120
+ });
121
+
122
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
123
+ })
124
+
125
+ it('isElementEquals test', async () => {
126
+ const testParameter = spy(new class extends TestArrayStatefulParameter {
127
+ getSettings(): ArrayParameterSetting {
128
+ return {
129
+ type: 'array',
130
+ isElementEqual: (desired, current) => current.includes(desired),
131
+ }
132
+ }
133
+
134
+ async refresh(): Promise<any> {
135
+ return [
136
+ '20.15.0',
137
+ '20.15.1'
138
+ ]
139
+ }
140
+ });
141
+
142
+ const resource = new class extends TestResource {
143
+ getSettings(): ResourceSettings<any> {
144
+ return {
145
+ id: 'type',
146
+ parameterSettings: { nodeVersions: { type: 'stateful', definition: testParameter } }
147
+ }
148
+ }
149
+
150
+ async refresh(): Promise<Partial<any> | null> {
151
+ return {};
152
+ }
153
+ }
154
+
155
+ const controller = new ResourceController(resource);
156
+ const plan = await controller.plan(
157
+ { type: 'type' },
158
+ { nodeVersions: ['20.15'] } as any,
159
+ null,
160
+ false
161
+ )
162
+
163
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
164
+ })
165
+
166
+ it('Accepts a string equals value', async () => {
167
+ const testParameter = spy(new class extends TestStatefulParameter {
168
+ getSettings(): ParameterSetting {
169
+ return {
170
+ type: 'string',
171
+ isEqual: 'version'
172
+ }
173
+ }
174
+
175
+ async refresh(): Promise<any> {
176
+ return '20.15.0';
177
+ }
178
+ });
179
+
180
+ const resource = new class extends TestResource {
181
+ getSettings(): ResourceSettings<any> {
182
+ return {
183
+ id: 'type',
184
+ parameterSettings: { propA: { type: 'stateful', definition: testParameter } }
185
+ }
186
+ }
187
+
188
+ async refresh(): Promise<Partial<any> | null> {
189
+ return {};
190
+ }
191
+ }
192
+
193
+ const controller = new ResourceController(resource);
194
+ const plan = await controller.plan(
195
+ { type: 'type' },
196
+ { propA: '20.15', } as any,
197
+ null,
198
+ false
199
+ )
200
+
201
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
202
+ })
203
+
204
+ it('Accepts a string isElementEquals value', async () => {
205
+ const testParameter = spy(new class extends TestStatefulParameter {
206
+ getSettings(): ParameterSetting {
207
+ return {
208
+ type: 'array',
209
+ isElementEqual: 'version'
210
+ }
211
+ }
212
+
213
+ async refresh(): Promise<any> {
214
+ return [
215
+ '20.15.0',
216
+ '20.18.0'
217
+ ]
218
+ }
219
+ });
220
+
221
+ const resource = new class extends TestResource {
222
+ getSettings(): ResourceSettings<any> {
223
+ return {
224
+ id: 'type',
225
+ parameterSettings: { propA: { type: 'stateful', definition: testParameter } }
226
+ }
227
+ }
228
+
229
+ async refresh(): Promise<Partial<any> | null> {
230
+ return {};
231
+ }
232
+ }
233
+
234
+ const controller = new ResourceController(resource);
235
+ const plan = await controller.plan(
236
+ { type: 'type' },
237
+ { propA: ['20.15', '20.18'] } as any,
238
+ null,
239
+ false
240
+ )
241
+
242
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
243
+ })
244
+ })
@@ -0,0 +1,111 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+
3
+ import { Plan } from '../plan/plan.js';
4
+ import { ParsedArrayParameterSetting, ParsedParameterSetting, } from '../resource/parsed-resource-settings.js';
5
+ import {
6
+ ArrayParameterSetting,
7
+ ParameterSetting,
8
+ resolveElementEqualsFn,
9
+ resolveEqualsFn,
10
+ } from '../resource/resource-settings.js';
11
+ import { ArrayStatefulParameter, StatefulParameter } from './stateful-parameter.js';
12
+
13
+ /**
14
+ * This class is analogous to what {@link ResourceController} is for {@link Resource}
15
+ * It's a bit messy because this class supports both {@link StatefulParameter} and {@link ArrayStatefulParameter}
16
+ */
17
+ export class StatefulParameterController<T extends StringIndexedObject, V extends T[keyof T]> {
18
+ readonly sp: ArrayStatefulParameter<T, V> | StatefulParameter<T, V>
19
+ readonly settings: ParameterSetting;
20
+ readonly parsedSettings: ParsedParameterSetting
21
+
22
+ private readonly isArrayStatefulParameter: boolean;
23
+
24
+ constructor(
25
+ statefulParameter: ArrayStatefulParameter<T, V> | StatefulParameter<T, V>
26
+ ) {
27
+ this.sp = statefulParameter;
28
+ this.settings = statefulParameter.getSettings();
29
+ this.isArrayStatefulParameter = this.calculateIsArrayStatefulParameter();
30
+
31
+ this.parsedSettings = (this.isArrayStatefulParameter || this.settings.type === 'array') ? {
32
+ ...this.settings,
33
+ isEqual: resolveEqualsFn(this.settings),
34
+ isElementEqual: resolveElementEqualsFn(this.settings as ArrayParameterSetting)
35
+ } as ParsedParameterSetting : {
36
+ ...this.settings,
37
+ isEqual: resolveEqualsFn(this.settings),
38
+ };
39
+ }
40
+
41
+ async refresh(desired: V | null, config: Partial<T>): Promise<V | null> {
42
+ return await this.sp.refresh(desired as any, config) as V | null;
43
+ }
44
+
45
+ async add(valueToAdd: V, plan: Plan<T>): Promise<void> {
46
+ if (!this.isArrayStatefulParameter) {
47
+ const sp = this.sp as StatefulParameter<T, V>;
48
+ return sp.add(valueToAdd, plan);
49
+ }
50
+
51
+ const sp = this.sp as ArrayStatefulParameter<any, any>;
52
+ const valuesToAdd = valueToAdd as Array<any>;
53
+ for (const value of valuesToAdd) {
54
+ await sp.addItem(value, plan);
55
+ }
56
+ }
57
+
58
+ async modify(newValue: V, previousValue: V, plan: Plan<T>): Promise<void> {
59
+ if (!this.isArrayStatefulParameter) {
60
+ const sp = this.sp as StatefulParameter<T, V>;
61
+ return sp.modify(newValue, previousValue, plan);
62
+ }
63
+
64
+ const sp = this.sp as ArrayStatefulParameter<any, any>;
65
+ const settings = this.parsedSettings as ParsedArrayParameterSetting;
66
+ const newValues = newValue as Array<unknown>[];
67
+ const previousValues = previousValue as Array<unknown>[];
68
+
69
+ // TODO: I don't think this works with duplicate elements. Solve at another time
70
+ const valuesToAdd = newValues.filter((n) => !previousValues.some((p) => {
71
+ if (settings.isElementEqual) {
72
+ return settings.isElementEqual!(n, p);
73
+ }
74
+
75
+ return n === p;
76
+ }));
77
+
78
+ const valuesToRemove = previousValues.filter((p) => !newValues.some((n) => {
79
+ if (settings.isElementEqual) {
80
+ return settings.isElementEqual!(n, p);
81
+ }
82
+
83
+ return n === p;
84
+ }));
85
+
86
+ for (const value of valuesToAdd) {
87
+ await sp.addItem(value, plan)
88
+ }
89
+
90
+ for (const value of valuesToRemove) {
91
+ await sp.removeItem(value, plan)
92
+ }
93
+ }
94
+
95
+ async remove(valueToRemove: V, plan: Plan<T>): Promise<void> {
96
+ if (!this.isArrayStatefulParameter) {
97
+ const sp = this.sp as StatefulParameter<T, V>;
98
+ return sp.remove(valueToRemove, plan);
99
+ }
100
+
101
+ const sp = this.sp as ArrayStatefulParameter<any, any>;
102
+ const valuesToRemove = valueToRemove as Array<any>;
103
+ for (const value of valuesToRemove) {
104
+ await sp.removeItem(value as V, plan);
105
+ }
106
+ }
107
+
108
+ private calculateIsArrayStatefulParameter() {
109
+ return 'addItem' in this.sp && 'removeItem' in this.sp;
110
+ }
111
+ }
@@ -0,0 +1,160 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+
3
+ import { Plan } from '../plan/plan.js';
4
+ import { ArrayParameterSetting, ParameterSetting } from '../resource/resource-settings.js';
5
+
6
+ /**
7
+ * A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
8
+ * is still tied to the overall lifecycle of a resource.
9
+ *
10
+ * **Examples include:**
11
+ * 1. Homebrew formulas are stateful parameters. They can be installed and uninstalled but they are still tied to the
12
+ * overall lifecycle of homebrew
13
+ * 2. Nvm installed node versions are stateful parameters. Nvm can install and uninstall different versions of Node but
14
+ * these versions are tied to the lifecycle of nvm. If nvm is uninstalled then so are the Node versions.
15
+ */
16
+ export abstract class StatefulParameter<T extends StringIndexedObject, V extends T[keyof T]> {
17
+
18
+ /**
19
+ * Parameter settings for the stateful parameter. Stateful parameters share the same parameter settings as
20
+ * regular parameters except that they cannot be of type 'stateful'. See {@link ParameterSetting} for more
21
+ * information on available settings.
22
+ *
23
+ * @return The parameter settings
24
+ */
25
+ getSettings(): ParameterSetting {
26
+ return {}
27
+ }
28
+
29
+ /**
30
+ * Refresh the status of the stateful parameter on the system. This method works similarly to {@link Resource.refresh}.
31
+ * Return the value of the stateful parameter or null if not found.
32
+ *
33
+ * @param desired The desired value of the user.
34
+ * @param config The desired config
35
+ *
36
+ * @return The value of the stateful parameter currently on the system or null if not found
37
+ */
38
+ abstract refresh(desired: V | null, config: Partial<T>): Promise<V | null>;
39
+
40
+ /**
41
+ * Create the stateful parameter on the system. This method is similar {@link Resource.create} except that its only
42
+ * applicable to the stateful parameter. For resource `CREATE` operations, this method will be called after the
43
+ * resource is successfully created. The add method is called when a ParameterChange is ADD in a plan. The add
44
+ * method is only called when the stateful parameter does not currently exist.
45
+ *
46
+ * **Example (Homebrew formula):**
47
+ * 1. Add is called with a value of:
48
+ * ```
49
+ * ['jq', 'jenv']
50
+ * ```
51
+ * 2. Add handles the request by calling `brew install --formulae jq jenv`
52
+ *
53
+ * @param valueToAdd The desired value of the stateful parameter.
54
+ * @param plan The overall plan that contains the ADD
55
+ */
56
+ abstract add(valueToAdd: V, plan: Plan<T>): Promise<void>;
57
+
58
+ /**
59
+ * Modify the state of a stateful parameter on the system. This method is similar to {@link Resource.modify} except that its only
60
+ * applicable to the stateful parameter.
61
+ *
62
+ * **Example (Git email parameter):**
63
+ * 1. Add is called with a value of:
64
+ * ```
65
+ * newValue: 'email+new@gmail.com', previousValue: 'email+old@gmail.com'
66
+ * ```
67
+ * 2. Modify handles the request by calling `git config --global user.email email+new@gmail.com`
68
+ *
69
+ * @param newValue The desired value of the stateful parameter
70
+ * @param previousValue The current value of the stateful parameter
71
+ * @param plan The overall plan
72
+ */
73
+ abstract modify(newValue: V, previousValue: V, plan: Plan<T>): Promise<void>;
74
+
75
+ /**
76
+ * Create the stateful parameter on the system. This method is similar {@link Resource.destroy} except that its only
77
+ * applicable to the stateful parameter. The remove method is only called when the stateful parameter already currently exist.
78
+ * This method corresponds to REMOVE parameter operations in a plan.
79
+ * For resource `DESTORY`, this method is only called if the {@link ResourceSettings.removeStatefulParametersBeforeDestroy}
80
+ * is set to true. This method will be called before the resource is destroyed.
81
+ *
82
+ * **Example (Homebrew formula):**
83
+ * 1. Remove is called with a value of:
84
+ * ```
85
+ * ['jq', 'jenv']
86
+ * ```
87
+ * 2. Remove handles the request by calling `brew uninstall --formulae jq jenv`
88
+ *
89
+ * @param valueToRemove The value to remove from the stateful parameter.
90
+ * @param plan The overall plan that contains the REMOVE
91
+ */
92
+ abstract remove(valueToRemove: V, plan: Plan<T>): Promise<void>;
93
+ }
94
+
95
+ /**
96
+ * A specialized version of {@link StatefulParameter } that is used for stateful parameters which are arrays.
97
+ * A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
98
+ * is still tied to the overall lifecycle of a resource.
99
+ *
100
+ * **Examples:**
101
+ * - Homebrew formulas are arrays
102
+ * - Pyenv python versions are arrays
103
+ */
104
+ export abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> {
105
+
106
+ /**
107
+ * Parameter settings for the stateful parameter. Stateful parameters share the same parameter settings as
108
+ * regular parameters except that they cannot be of type 'stateful'. See {@link ParameterSetting} for more
109
+ * information on available settings. Type must be 'array'.
110
+ *
111
+ * @return The parameter settings
112
+ */
113
+ getSettings(): ArrayParameterSetting {
114
+ return { type: 'array' }
115
+ }
116
+
117
+ /**
118
+ * See {@link StatefulParameter.refresh} for more info.
119
+ *
120
+ * @param desired The desired value to refresh
121
+ * @param config The desired config
122
+ *
123
+ * @return The current value on the system or null if not found.
124
+ */
125
+ abstract refresh(desired: V[] | null, config: Partial<T>): Promise<V[] | null>;
126
+
127
+ /**
128
+ * Helper method that gets called when individual elements of the array need to be added. See {@link StatefulParameter.add}
129
+ * for more information.
130
+ *
131
+ * Example (Homebrew formula):
132
+ * 1. The stateful parameter receives an input of:
133
+ * ```
134
+ * ['jq', 'jenv', 'docker']
135
+ * ```
136
+ * 2. Internally the stateful parameter will iterate the array and call `addItem` for each element
137
+ * 3. Override addItem and install each formula using `brew install --formula jq`
138
+ *
139
+ * @param item The item to add (install)
140
+ * @param plan The overall plan
141
+ */
142
+ abstract addItem(item: V, plan: Plan<T>): Promise<void>;
143
+
144
+ /**
145
+ * Helper method that gets called when individual elements of the array need to be removed. See {@link StatefulParameter.remove}
146
+ * for more information.
147
+ *
148
+ * Example (Homebrew formula):
149
+ * 1. The stateful parameter receives an input of:
150
+ * ```
151
+ * ['jq', 'jenv', 'docker']
152
+ * ```
153
+ * 2. Internally the stateful parameter will iterate the array and call `removeItem` for each element
154
+ * 3. Override removeItem and uninstall each formula using `brew uninstall --formula jq`
155
+ *
156
+ * @param item The item to remove (uninstall)
157
+ * @param plan The overall plan
158
+ */
159
+ abstract removeItem(item: V, plan: Plan<T>): Promise<void>;
160
+ }
@@ -0,0 +1,11 @@
1
+ export function debugLog(message: any): void {
2
+ if (process.env.DEBUG) {
3
+ console.log(message);
4
+ }
5
+ }
6
+
7
+ export function debugWrite(message: any): void {
8
+ if (process.env.DEBUG) {
9
+ process.stdout.write(message);
10
+ }
11
+ }
@@ -0,0 +1,7 @@
1
+ import { describe, it } from 'vitest';
2
+
3
+ describe('File utils tests', { timeout: 100_000_000 }, () => {
4
+ it('Can download a file', async () => {
5
+ // await FileUtils.downloadFile('https://download.jetbrains.com/webstorm/WebStorm-2025.3.1-aarch64.dmg?_gl=1*1huoi7o*_gcl_aw*R0NMLjE3NjU3NDAwMTcuQ2p3S0NBaUEzZm5KQmhBZ0Vpd0F5cW1ZNVhLVENlbHJOcTk2YXdjZVlfMS1wdE91MXc0WDk2bFJkVDM3QURhUFNJMUtwNVVSVUhxWTJob0NuZ0FRQXZEX0J3RQ..*_gcl_au*MjA0MDQ0MjE2My4xNzYzNjQzNzMz*FPAU*MjA0MDQ0MjE2My4xNzYzNjQzNzMz*_ga*MTYxMDg4MTkzMi4xNzYzNjQzNzMz*_ga_9J976DJZ68*czE3NjYzNjI5ODAkbzEyJGcxJHQxNzY2MzYzMDQwJGo2MCRsMCRoMA..', path.join(process.cwd(), 'google.html'));
6
+ })
7
+ })