codify-plugin-lib 1.0.100 → 1.0.102
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.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plan/change-set.d.ts +2 -2
- package/dist/plan/plan.js +11 -8
- package/dist/resource/config-parser.d.ts +2 -2
- package/dist/resource/parsed-resource-settings.d.ts +17 -4
- package/dist/resource/parsed-resource-settings.js +25 -3
- package/dist/resource/resource-controller.js +2 -1
- package/dist/resource/resource-settings.d.ts +7 -6
- package/dist/resource/resource-settings.js +21 -5
- package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
- package/dist/stateful-parameter/stateful-parameter-controller.js +82 -0
- package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
- package/dist/stateful-parameter/stateful-parameter.js +43 -0
- package/dist/utils/utils.d.ts +1 -2
- package/dist/utils/utils.js +3 -2
- package/package.json +4 -2
- package/src/index.ts +1 -1
- package/src/plan/change-set.ts +5 -5
- package/src/plan/plan.test.ts +6 -1
- package/src/plan/plan.ts +21 -16
- package/src/resource/config-parser.ts +3 -3
- package/src/resource/parsed-resource-settings.ts +53 -6
- package/src/resource/resource-controller.ts +2 -1
- package/src/resource/resource-settings.test.ts +101 -0
- package/src/resource/resource-settings.ts +34 -9
- package/src/{resource/stateful-parameter.test.ts → stateful-parameter/stateful-parameter-controller.test.ts} +91 -12
- package/src/stateful-parameter/stateful-parameter-controller.ts +112 -0
- package/src/{resource → stateful-parameter}/stateful-parameter.ts +9 -67
- package/src/utils/test-utils.test.ts +1 -1
- package/src/utils/utils.ts +9 -4
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,6 @@ export * from './plugin/plugin.js';
|
|
|
7
7
|
export * from './resource/parsed-resource-settings.js';
|
|
8
8
|
export * from './resource/resource.js';
|
|
9
9
|
export * from './resource/resource-settings.js';
|
|
10
|
-
export * from './
|
|
10
|
+
export * from './stateful-parameter/stateful-parameter.js';
|
|
11
11
|
export * from './utils/utils.js';
|
|
12
12
|
export declare function runPlugin(plugin: Plugin): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export * from './plugin/plugin.js';
|
|
|
7
7
|
export * from './resource/parsed-resource-settings.js';
|
|
8
8
|
export * from './resource/resource.js';
|
|
9
9
|
export * from './resource/resource-settings.js';
|
|
10
|
-
export * from './
|
|
10
|
+
export * from './stateful-parameter/stateful-parameter.js';
|
|
11
11
|
export * from './utils/utils.js';
|
|
12
12
|
export async function runPlugin(plugin) {
|
|
13
13
|
const messageHandler = new MessageHandler(plugin);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
2
|
-
import {
|
|
2
|
+
import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
|
|
3
3
|
/**
|
|
4
4
|
* A parameter change describes a parameter level change to a resource.
|
|
5
5
|
*/
|
|
@@ -30,7 +30,7 @@ export declare class ChangeSet<T extends StringIndexedObject> {
|
|
|
30
30
|
static empty<T extends StringIndexedObject>(): ChangeSet<T>;
|
|
31
31
|
static create<T extends StringIndexedObject>(desired: Partial<T>): ChangeSet<T>;
|
|
32
32
|
static destroy<T extends StringIndexedObject>(current: Partial<T>): ChangeSet<T>;
|
|
33
|
-
static calculateModification<T extends StringIndexedObject>(desired: Partial<T>, current: Partial<T>, parameterSettings?: Partial<Record<keyof T,
|
|
33
|
+
static calculateModification<T extends StringIndexedObject>(desired: Partial<T>, current: Partial<T>, parameterSettings?: Partial<Record<keyof T, ParsedParameterSetting>>): ChangeSet<T>;
|
|
34
34
|
/**
|
|
35
35
|
* Calculates the differences between the desired and current parameters,
|
|
36
36
|
* and returns a list of parameter changes that describe what needs to be added,
|
package/dist/plan/plan.js
CHANGED
|
@@ -144,10 +144,14 @@ export class Plan {
|
|
|
144
144
|
.filter(([k]) => keys.has(k)));
|
|
145
145
|
}
|
|
146
146
|
function isArrayParameterWithFiltering(k, v) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
if (settings.parameterSettings?.[k]?.type === 'stateful') {
|
|
148
|
+
const statefulSetting = settings.parameterSettings[k];
|
|
149
|
+
return statefulSetting.nestedSettings.type === 'array' &&
|
|
150
|
+
(statefulSetting.nestedSettings.filterInStatelessMode ?? true)
|
|
151
|
+
&& Array.isArray(v);
|
|
152
|
+
}
|
|
153
|
+
return settings.parameterSettings?.[k]?.type === 'array'
|
|
154
|
+
&& ((settings.parameterSettings?.[k]).filterInStatelessMode ?? true)
|
|
151
155
|
&& Array.isArray(v);
|
|
152
156
|
}
|
|
153
157
|
// For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
|
|
@@ -155,11 +159,10 @@ export class Plan {
|
|
|
155
159
|
const desiredArray = desired[k];
|
|
156
160
|
const matcher = settings.parameterSettings[k].type === 'stateful'
|
|
157
161
|
? settings.parameterSettings[k]
|
|
158
|
-
.
|
|
159
|
-
.
|
|
160
|
-
.isElementEqual ?? ((a, b) => a === b)
|
|
162
|
+
.nestedSettings
|
|
163
|
+
.isElementEqual
|
|
161
164
|
: settings.parameterSettings[k]
|
|
162
|
-
.isElementEqual
|
|
165
|
+
.isElementEqual;
|
|
163
166
|
const desiredCopy = [...desiredArray];
|
|
164
167
|
const currentCopy = [...v];
|
|
165
168
|
const result = [];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
2
|
-
import {
|
|
2
|
+
import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
|
|
3
3
|
export declare class ConfigParser<T extends StringIndexedObject> {
|
|
4
4
|
private readonly desiredConfig;
|
|
5
5
|
private readonly stateConfig;
|
|
6
6
|
private statefulParametersMap;
|
|
7
|
-
constructor(desiredConfig: Partial<T> & ResourceConfig | null, stateConfig: Partial<T> & ResourceConfig | null, statefulParameters: Map<keyof T,
|
|
7
|
+
constructor(desiredConfig: Partial<T> & ResourceConfig | null, stateConfig: Partial<T> & ResourceConfig | null, statefulParameters: Map<keyof T, StatefulParameterController<T, T[keyof T]>>);
|
|
8
8
|
get coreParameters(): ResourceConfig;
|
|
9
9
|
get desiredParameters(): Partial<T> | null;
|
|
10
10
|
get stateParameters(): Partial<T> | null;
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { StringIndexedObject } from 'codify-schemas';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
|
|
3
|
+
import { ArrayParameterSetting, DefaultParameterSetting, ResourceSettings } from './resource-settings.js';
|
|
4
|
+
export interface ParsedStatefulParameterSetting extends DefaultParameterSetting {
|
|
5
|
+
type: 'stateful';
|
|
6
|
+
controller: StatefulParameterController<any, unknown>;
|
|
7
|
+
order?: number;
|
|
8
|
+
nestedSettings: ParsedParameterSetting;
|
|
9
|
+
}
|
|
10
|
+
export type ParsedArrayParameterSetting = {
|
|
11
|
+
isElementEqual: (a: unknown, b: unknown) => boolean;
|
|
12
|
+
isEqual: (a: unknown, b: unknown) => boolean;
|
|
13
|
+
} & ArrayParameterSetting;
|
|
14
|
+
export type ParsedParameterSetting = {
|
|
15
|
+
isEqual: (desired: unknown, current: unknown) => boolean;
|
|
16
|
+
} & (DefaultParameterSetting | ParsedArrayParameterSetting | ParsedStatefulParameterSetting);
|
|
4
17
|
export declare class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
|
|
5
18
|
private cache;
|
|
6
19
|
id: string;
|
|
@@ -14,8 +27,8 @@ export declare class ParsedResourceSettings<T extends StringIndexedObject> imple
|
|
|
14
27
|
private settings;
|
|
15
28
|
constructor(settings: ResourceSettings<T>);
|
|
16
29
|
get typeId(): string;
|
|
17
|
-
get statefulParameters(): Map<keyof T,
|
|
18
|
-
get parameterSettings(): Record<keyof T,
|
|
30
|
+
get statefulParameters(): Map<keyof T, StatefulParameterController<T, T[keyof T]>>;
|
|
31
|
+
get parameterSettings(): Record<keyof T, ParsedParameterSetting>;
|
|
19
32
|
get defaultValues(): Partial<Record<keyof T, unknown>>;
|
|
20
33
|
get inputTransformations(): Partial<Record<keyof T, (a: unknown) => unknown>>;
|
|
21
34
|
get statefulParameterOrder(): Map<keyof T, number>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
|
|
2
|
+
import { resolveEqualsFn, resolveFnFromEqualsFnOrString, resolveParameterTransformFn } from './resource-settings.js';
|
|
2
3
|
export class ParsedResourceSettings {
|
|
3
4
|
cache = new Map();
|
|
4
5
|
id;
|
|
@@ -25,7 +26,10 @@ export class ParsedResourceSettings {
|
|
|
25
26
|
return this.getFromCacheOrCreate('statefulParameters', () => {
|
|
26
27
|
const statefulParameters = Object.entries(this.settings.parameterSettings ?? {})
|
|
27
28
|
.filter(([, p]) => p?.type === 'stateful')
|
|
28
|
-
.map(([k, v]) => [
|
|
29
|
+
.map(([k, v]) => [
|
|
30
|
+
k,
|
|
31
|
+
new StatefulParameterController(v.definition)
|
|
32
|
+
]);
|
|
29
33
|
return new Map(statefulParameters);
|
|
30
34
|
});
|
|
31
35
|
}
|
|
@@ -34,7 +38,25 @@ export class ParsedResourceSettings {
|
|
|
34
38
|
const settings = Object.entries(this.settings.parameterSettings ?? {})
|
|
35
39
|
.map(([k, v]) => [k, v])
|
|
36
40
|
.map(([k, v]) => {
|
|
37
|
-
v.isEqual = resolveEqualsFn(v
|
|
41
|
+
v.isEqual = resolveEqualsFn(v);
|
|
42
|
+
if (v.type === 'stateful') {
|
|
43
|
+
const spController = this.statefulParameters.get(k);
|
|
44
|
+
const parsed = {
|
|
45
|
+
...v,
|
|
46
|
+
controller: spController,
|
|
47
|
+
nestedSettings: spController?.parsedSettings,
|
|
48
|
+
definition: undefined,
|
|
49
|
+
};
|
|
50
|
+
return [k, parsed];
|
|
51
|
+
}
|
|
52
|
+
if (v.type === 'array') {
|
|
53
|
+
const parsed = {
|
|
54
|
+
...v,
|
|
55
|
+
isElementEqual: resolveFnFromEqualsFnOrString(v.isElementEqual)
|
|
56
|
+
?? ((a, b) => a === b),
|
|
57
|
+
};
|
|
58
|
+
return [k, parsed];
|
|
59
|
+
}
|
|
38
60
|
return [k, v];
|
|
39
61
|
});
|
|
40
62
|
return Object.fromEntries(settings);
|
|
@@ -37,7 +37,8 @@ export class ResourceController {
|
|
|
37
37
|
const { parameters, coreParameters } = splitUserConfig(configToValidate);
|
|
38
38
|
this.addDefaultValues(parameters);
|
|
39
39
|
if (this.schemaValidator) {
|
|
40
|
-
|
|
40
|
+
// Schema validator uses pre transformation parameters
|
|
41
|
+
const isValid = this.schemaValidator(desiredConfig);
|
|
41
42
|
if (!isValid) {
|
|
42
43
|
return {
|
|
43
44
|
isValid: false,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StringIndexedObject } from 'codify-schemas';
|
|
2
|
-
import { StatefulParameter } from '
|
|
2
|
+
import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
|
|
3
3
|
/**
|
|
4
4
|
* The configuration and settings for a resource.
|
|
5
5
|
*/
|
|
@@ -107,7 +107,7 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
107
107
|
* config with desired config. Certain types will have additional options to help support it. For example the type
|
|
108
108
|
* stateful requires a stateful parameter definition and type array takes an isElementEqual method.
|
|
109
109
|
*/
|
|
110
|
-
export type ParameterSettingType = 'any' | 'array' | 'boolean' | 'directory' | 'number' | 'setting' | 'stateful' | 'string' | 'version';
|
|
110
|
+
export type ParameterSettingType = 'any' | 'array' | 'boolean' | 'directory' | 'number' | 'object' | 'setting' | 'stateful' | 'string' | 'version';
|
|
111
111
|
/**
|
|
112
112
|
* Typing information for the parameter setting. This represents a setting on a specific parameter within a
|
|
113
113
|
* resource. Options for configuring parameters operations including overriding the equals function, adding default values
|
|
@@ -146,7 +146,7 @@ export interface DefaultParameterSetting {
|
|
|
146
146
|
*
|
|
147
147
|
* @return Return true if equal
|
|
148
148
|
*/
|
|
149
|
-
isEqual?: (desired: any, current: any) => boolean;
|
|
149
|
+
isEqual?: ((desired: any, current: any) => boolean) | ParameterSettingType;
|
|
150
150
|
/**
|
|
151
151
|
* Chose if the resource can be modified instead of re-created when there is a change to this parameter.
|
|
152
152
|
* Defaults to false (re-create).
|
|
@@ -172,7 +172,7 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
|
|
|
172
172
|
*
|
|
173
173
|
* @return Return true if desired is equivalent to current.
|
|
174
174
|
*/
|
|
175
|
-
isElementEqual?: (desired: any, current: any) => boolean;
|
|
175
|
+
isElementEqual?: ((desired: any, current: any) => boolean) | ParameterSettingType;
|
|
176
176
|
/**
|
|
177
177
|
* Filter the contents of the refreshed array by the desired. This way items currently on the system but not
|
|
178
178
|
* in desired don't show up in the plan.
|
|
@@ -208,11 +208,12 @@ export interface StatefulParameterSetting extends DefaultParameterSetting {
|
|
|
208
208
|
* as a resource and taps, formulas and casks are represented as a stateful parameter. A formula can be installed,
|
|
209
209
|
* modified and removed (has state) but it is still tied to the overall lifecycle of homebrew.
|
|
210
210
|
*/
|
|
211
|
-
definition: StatefulParameter<any, unknown>;
|
|
211
|
+
definition: ArrayStatefulParameter<any, unknown> | StatefulParameter<any, unknown>;
|
|
212
212
|
/**
|
|
213
213
|
* The order multiple stateful parameters should be applied in. The order is applied in ascending order (1, 2, 3...).
|
|
214
214
|
*/
|
|
215
215
|
order?: number;
|
|
216
216
|
}
|
|
217
|
-
export declare function resolveEqualsFn(parameter: ParameterSetting
|
|
217
|
+
export declare function resolveEqualsFn(parameter: ParameterSetting): (desired: unknown, current: unknown) => boolean;
|
|
218
|
+
export declare function resolveFnFromEqualsFnOrString(fnOrString: ((a: unknown, b: unknown) => boolean) | ParameterSettingType | undefined): ((a: unknown, b: unknown) => boolean) | undefined;
|
|
218
219
|
export declare function resolveParameterTransformFn(parameter: ParameterSetting): ((input: any) => Promise<any> | any) | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import isObjectsEqual from 'lodash.isequal';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { areArraysEqual, untildify } from '../utils/utils.js';
|
|
3
4
|
const ParameterEqualsDefaults = {
|
|
@@ -6,16 +7,31 @@ const ParameterEqualsDefaults = {
|
|
|
6
7
|
'number': (a, b) => Number(a) === Number(b),
|
|
7
8
|
'string': (a, b) => String(a) === String(b),
|
|
8
9
|
'version': (desired, current) => String(current).includes(String(desired)),
|
|
9
|
-
'setting': (
|
|
10
|
+
'setting': () => true,
|
|
11
|
+
'object': isObjectsEqual,
|
|
10
12
|
};
|
|
11
|
-
export function resolveEqualsFn(parameter
|
|
13
|
+
export function resolveEqualsFn(parameter) {
|
|
14
|
+
const isEqual = resolveFnFromEqualsFnOrString(parameter.isEqual);
|
|
12
15
|
if (parameter.type === 'array') {
|
|
13
|
-
|
|
16
|
+
const arrayParameter = parameter;
|
|
17
|
+
const isElementEqual = resolveFnFromEqualsFnOrString(arrayParameter.isElementEqual);
|
|
18
|
+
return isEqual ?? areArraysEqual.bind(areArraysEqual, isElementEqual);
|
|
14
19
|
}
|
|
15
20
|
if (parameter.type === 'stateful') {
|
|
16
|
-
return resolveEqualsFn(parameter.definition.getSettings()
|
|
21
|
+
return resolveEqualsFn(parameter.definition.getSettings());
|
|
17
22
|
}
|
|
18
|
-
return
|
|
23
|
+
return isEqual ?? ParameterEqualsDefaults[parameter.type] ?? (((a, b) => a === b));
|
|
24
|
+
}
|
|
25
|
+
// This resolves the fn if it is a string.
|
|
26
|
+
// A string can be specified to use a default equals method
|
|
27
|
+
export function resolveFnFromEqualsFnOrString(fnOrString) {
|
|
28
|
+
if (fnOrString && typeof fnOrString === 'string') {
|
|
29
|
+
if (!ParameterEqualsDefaults[fnOrString]) {
|
|
30
|
+
throw new Error(`isEqual of type ${fnOrString} was not found`);
|
|
31
|
+
}
|
|
32
|
+
return ParameterEqualsDefaults[fnOrString];
|
|
33
|
+
}
|
|
34
|
+
return fnOrString;
|
|
19
35
|
}
|
|
20
36
|
const ParameterTransformationDefaults = {
|
|
21
37
|
'directory': (a) => path.resolve(untildify(String(a))),
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
import { Plan } from '../plan/plan.js';
|
|
3
|
+
import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
|
|
4
|
+
import { ParameterSetting } from '../resource/resource-settings.js';
|
|
5
|
+
import { ArrayStatefulParameter, StatefulParameter } from './stateful-parameter.js';
|
|
6
|
+
/**
|
|
7
|
+
* This class is analogous to what {@link ResourceController} is for {@link Resource}
|
|
8
|
+
* It's a bit messy because this class supports both {@link StatefulParameter} and {@link ArrayStatefulParameter}
|
|
9
|
+
*/
|
|
10
|
+
export declare class StatefulParameterController<T extends StringIndexedObject, V extends T[keyof T]> {
|
|
11
|
+
readonly sp: ArrayStatefulParameter<T, V> | StatefulParameter<T, V>;
|
|
12
|
+
readonly settings: ParameterSetting;
|
|
13
|
+
readonly parsedSettings: ParsedParameterSetting;
|
|
14
|
+
private readonly isArrayStatefulParameter;
|
|
15
|
+
constructor(statefulParameter: ArrayStatefulParameter<T, V> | StatefulParameter<T, V>);
|
|
16
|
+
refresh(desired: V | null, config: Partial<T>): Promise<V | null>;
|
|
17
|
+
add(valueToAdd: V, plan: Plan<T>): Promise<void>;
|
|
18
|
+
modify(newValue: V, previousValue: V, plan: Plan<T>): Promise<void>;
|
|
19
|
+
remove(valueToRemove: V, plan: Plan<T>): Promise<void>;
|
|
20
|
+
private calculateIsArrayStatefulParameter;
|
|
21
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { resolveEqualsFn, resolveFnFromEqualsFnOrString } from '../resource/resource-settings.js';
|
|
2
|
+
/**
|
|
3
|
+
* This class is analogous to what {@link ResourceController} is for {@link Resource}
|
|
4
|
+
* It's a bit messy because this class supports both {@link StatefulParameter} and {@link ArrayStatefulParameter}
|
|
5
|
+
*/
|
|
6
|
+
export class StatefulParameterController {
|
|
7
|
+
sp;
|
|
8
|
+
settings;
|
|
9
|
+
parsedSettings;
|
|
10
|
+
isArrayStatefulParameter;
|
|
11
|
+
constructor(statefulParameter) {
|
|
12
|
+
this.sp = statefulParameter;
|
|
13
|
+
this.settings = statefulParameter.getSettings();
|
|
14
|
+
this.isArrayStatefulParameter = this.calculateIsArrayStatefulParameter();
|
|
15
|
+
this.parsedSettings = (this.isArrayStatefulParameter || this.settings.type === 'array') ? {
|
|
16
|
+
...this.settings,
|
|
17
|
+
isEqual: resolveEqualsFn(this.settings),
|
|
18
|
+
isElementEqual: resolveFnFromEqualsFnOrString(this.settings.isElementEqual)
|
|
19
|
+
?? ((a, b) => a === b)
|
|
20
|
+
} : {
|
|
21
|
+
...this.settings,
|
|
22
|
+
isEqual: resolveEqualsFn(this.settings),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async refresh(desired, config) {
|
|
26
|
+
return await this.sp.refresh(desired, config);
|
|
27
|
+
}
|
|
28
|
+
async add(valueToAdd, plan) {
|
|
29
|
+
if (!this.isArrayStatefulParameter) {
|
|
30
|
+
const sp = this.sp;
|
|
31
|
+
return sp.add(valueToAdd, plan);
|
|
32
|
+
}
|
|
33
|
+
const sp = this.sp;
|
|
34
|
+
const valuesToAdd = valueToAdd;
|
|
35
|
+
for (const value of valuesToAdd) {
|
|
36
|
+
await sp.addItem(value, plan);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async modify(newValue, previousValue, plan) {
|
|
40
|
+
if (!this.isArrayStatefulParameter) {
|
|
41
|
+
const sp = this.sp;
|
|
42
|
+
return sp.modify(newValue, previousValue, plan);
|
|
43
|
+
}
|
|
44
|
+
const sp = this.sp;
|
|
45
|
+
const settings = this.parsedSettings;
|
|
46
|
+
const newValues = newValue;
|
|
47
|
+
const previousValues = previousValue;
|
|
48
|
+
// TODO: I don't think this works with duplicate elements. Solve at another time
|
|
49
|
+
const valuesToAdd = newValues.filter((n) => !previousValues.some((p) => {
|
|
50
|
+
if (settings.isElementEqual) {
|
|
51
|
+
return settings.isElementEqual(n, p);
|
|
52
|
+
}
|
|
53
|
+
return n === p;
|
|
54
|
+
}));
|
|
55
|
+
const valuesToRemove = previousValues.filter((p) => !newValues.some((n) => {
|
|
56
|
+
if (settings.isElementEqual) {
|
|
57
|
+
return settings.isElementEqual(n, p);
|
|
58
|
+
}
|
|
59
|
+
return n === p;
|
|
60
|
+
}));
|
|
61
|
+
for (const value of valuesToAdd) {
|
|
62
|
+
await sp.addItem(value, plan);
|
|
63
|
+
}
|
|
64
|
+
for (const value of valuesToRemove) {
|
|
65
|
+
await sp.removeItem(value, plan);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async remove(valueToRemove, plan) {
|
|
69
|
+
if (!this.isArrayStatefulParameter) {
|
|
70
|
+
const sp = this.sp;
|
|
71
|
+
return sp.remove(valueToRemove, plan);
|
|
72
|
+
}
|
|
73
|
+
const sp = this.sp;
|
|
74
|
+
const valuesToRemove = valueToRemove;
|
|
75
|
+
for (const value of valuesToRemove) {
|
|
76
|
+
await sp.removeItem(value, plan);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
calculateIsArrayStatefulParameter() {
|
|
80
|
+
return Object.hasOwn(this.sp, 'addItem') && Object.hasOwn(this.sp, 'removeItem');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
import { Plan } from '../plan/plan.js';
|
|
3
|
+
import { ArrayParameterSetting, ParameterSetting } from '../resource/resource-settings.js';
|
|
4
|
+
/**
|
|
5
|
+
* A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
|
|
6
|
+
* is still tied to the overall lifecycle of a resource.
|
|
7
|
+
*
|
|
8
|
+
* **Examples include:**
|
|
9
|
+
* 1. Homebrew formulas are stateful parameters. They can be installed and uninstalled but they are still tied to the
|
|
10
|
+
* overall lifecycle of homebrew
|
|
11
|
+
* 2. Nvm installed node versions are stateful parameters. Nvm can install and uninstall different versions of Node but
|
|
12
|
+
* these versions are tied to the lifecycle of nvm. If nvm is uninstalled then so are the Node versions.
|
|
13
|
+
*/
|
|
14
|
+
export declare abstract class StatefulParameter<T extends StringIndexedObject, V extends T[keyof T]> {
|
|
15
|
+
/**
|
|
16
|
+
* Parameter settings for the stateful parameter. Stateful parameters share the same parameter settings as
|
|
17
|
+
* regular parameters except that they cannot be of type 'stateful'. See {@link ParameterSetting} for more
|
|
18
|
+
* information on available settings.
|
|
19
|
+
*
|
|
20
|
+
* @return The parameter settings
|
|
21
|
+
*/
|
|
22
|
+
getSettings(): ParameterSetting;
|
|
23
|
+
/**
|
|
24
|
+
* Refresh the status of the stateful parameter on the system. This method works similarly to {@link Resource.refresh}.
|
|
25
|
+
* Return the value of the stateful parameter or null if not found.
|
|
26
|
+
*
|
|
27
|
+
* @param desired The desired value of the user.
|
|
28
|
+
* @param config The desired config
|
|
29
|
+
*
|
|
30
|
+
* @return The value of the stateful parameter currently on the system or null if not found
|
|
31
|
+
*/
|
|
32
|
+
abstract refresh(desired: V | null, config: Partial<T>): Promise<V | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Create the stateful parameter on the system. This method is similar {@link Resource.create} except that its only
|
|
35
|
+
* applicable to the stateful parameter. For resource `CREATE` operations, this method will be called after the
|
|
36
|
+
* resource is successfully created. The add method is called when a ParameterChange is ADD in a plan. The add
|
|
37
|
+
* method is only called when the stateful parameter does not currently exist.
|
|
38
|
+
*
|
|
39
|
+
* **Example (Homebrew formula):**
|
|
40
|
+
* 1. Add is called with a value of:
|
|
41
|
+
* ```
|
|
42
|
+
* ['jq', 'jenv']
|
|
43
|
+
* ```
|
|
44
|
+
* 2. Add handles the request by calling `brew install --formulae jq jenv`
|
|
45
|
+
*
|
|
46
|
+
* @param valueToAdd The desired value of the stateful parameter.
|
|
47
|
+
* @param plan The overall plan that contains the ADD
|
|
48
|
+
*/
|
|
49
|
+
abstract add(valueToAdd: V, plan: Plan<T>): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Modify the state of a stateful parameter on the system. This method is similar to {@link Resource.modify} except that its only
|
|
52
|
+
* applicable to the stateful parameter.
|
|
53
|
+
*
|
|
54
|
+
* **Example (Git email parameter):**
|
|
55
|
+
* 1. Add is called with a value of:
|
|
56
|
+
* ```
|
|
57
|
+
* newValue: 'email+new@gmail.com', previousValue: 'email+old@gmail.com'
|
|
58
|
+
* ```
|
|
59
|
+
* 2. Modify handles the request by calling `git config --global user.email email+new@gmail.com`
|
|
60
|
+
*
|
|
61
|
+
* @param newValue The desired value of the stateful parameter
|
|
62
|
+
* @param previousValue The current value of the stateful parameter
|
|
63
|
+
* @param plan The overall plan
|
|
64
|
+
*/
|
|
65
|
+
abstract modify(newValue: V, previousValue: V, plan: Plan<T>): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Create the stateful parameter on the system. This method is similar {@link Resource.destroy} except that its only
|
|
68
|
+
* applicable to the stateful parameter. The remove method is only called when the stateful parameter already currently exist.
|
|
69
|
+
* This method corresponds to REMOVE parameter operations in a plan.
|
|
70
|
+
* For resource `DESTORY`, this method is only called if the {@link ResourceSettings.removeStatefulParametersBeforeDestroy}
|
|
71
|
+
* is set to true. This method will be called before the resource is destroyed.
|
|
72
|
+
*
|
|
73
|
+
* **Example (Homebrew formula):**
|
|
74
|
+
* 1. Remove is called with a value of:
|
|
75
|
+
* ```
|
|
76
|
+
* ['jq', 'jenv']
|
|
77
|
+
* ```
|
|
78
|
+
* 2. Remove handles the request by calling `brew uninstall --formulae jq jenv`
|
|
79
|
+
*
|
|
80
|
+
* @param valueToRemove The value to remove from the stateful parameter.
|
|
81
|
+
* @param plan The overall plan that contains the REMOVE
|
|
82
|
+
*/
|
|
83
|
+
abstract remove(valueToRemove: V, plan: Plan<T>): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A specialized version of {@link StatefulParameter } that is used for stateful parameters which are arrays.
|
|
87
|
+
* A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
|
|
88
|
+
* is still tied to the overall lifecycle of a resource.
|
|
89
|
+
*
|
|
90
|
+
* **Examples:**
|
|
91
|
+
* - Homebrew formulas are arrays
|
|
92
|
+
* - Pyenv python versions are arrays
|
|
93
|
+
*/
|
|
94
|
+
export declare abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> {
|
|
95
|
+
/**
|
|
96
|
+
* Parameter settings for the stateful parameter. Stateful parameters share the same parameter settings as
|
|
97
|
+
* regular parameters except that they cannot be of type 'stateful'. See {@link ParameterSetting} for more
|
|
98
|
+
* information on available settings. Type must be 'array'.
|
|
99
|
+
*
|
|
100
|
+
* @return The parameter settings
|
|
101
|
+
*/
|
|
102
|
+
getSettings(): ArrayParameterSetting;
|
|
103
|
+
/**
|
|
104
|
+
* See {@link StatefulParameter.refresh} for more info.
|
|
105
|
+
*
|
|
106
|
+
* @param desired The desired value to refresh
|
|
107
|
+
* @param config The desired config
|
|
108
|
+
*
|
|
109
|
+
* @return The current value on the system or null if not found.
|
|
110
|
+
*/
|
|
111
|
+
abstract refresh(desired: V[] | null, config: Partial<T>): Promise<V[] | null>;
|
|
112
|
+
/**
|
|
113
|
+
* Helper method that gets called when individual elements of the array need to be added. See {@link StatefulParameter.add}
|
|
114
|
+
* for more information.
|
|
115
|
+
*
|
|
116
|
+
* Example (Homebrew formula):
|
|
117
|
+
* 1. The stateful parameter receives an input of:
|
|
118
|
+
* ```
|
|
119
|
+
* ['jq', 'jenv', 'docker']
|
|
120
|
+
* ```
|
|
121
|
+
* 2. Internally the stateful parameter will iterate the array and call `addItem` for each element
|
|
122
|
+
* 3. Override addItem and install each formula using `brew install --formula jq`
|
|
123
|
+
*
|
|
124
|
+
* @param item The item to add (install)
|
|
125
|
+
* @param plan The overall plan
|
|
126
|
+
*/
|
|
127
|
+
abstract addItem(item: V, plan: Plan<T>): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Helper method that gets called when individual elements of the array need to be removed. See {@link StatefulParameter.remove}
|
|
130
|
+
* for more information.
|
|
131
|
+
*
|
|
132
|
+
* Example (Homebrew formula):
|
|
133
|
+
* 1. The stateful parameter receives an input of:
|
|
134
|
+
* ```
|
|
135
|
+
* ['jq', 'jenv', 'docker']
|
|
136
|
+
* ```
|
|
137
|
+
* 2. Internally the stateful parameter will iterate the array and call `removeItem` for each element
|
|
138
|
+
* 3. Override removeItem and uninstall each formula using `brew uninstall --formula jq`
|
|
139
|
+
*
|
|
140
|
+
* @param item The item to remove (uninstall)
|
|
141
|
+
* @param plan The overall plan
|
|
142
|
+
*/
|
|
143
|
+
abstract removeItem(item: V, plan: Plan<T>): Promise<void>;
|
|
144
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
|
|
3
|
+
* is still tied to the overall lifecycle of a resource.
|
|
4
|
+
*
|
|
5
|
+
* **Examples include:**
|
|
6
|
+
* 1. Homebrew formulas are stateful parameters. They can be installed and uninstalled but they are still tied to the
|
|
7
|
+
* overall lifecycle of homebrew
|
|
8
|
+
* 2. Nvm installed node versions are stateful parameters. Nvm can install and uninstall different versions of Node but
|
|
9
|
+
* these versions are tied to the lifecycle of nvm. If nvm is uninstalled then so are the Node versions.
|
|
10
|
+
*/
|
|
11
|
+
export class StatefulParameter {
|
|
12
|
+
/**
|
|
13
|
+
* Parameter settings for the stateful parameter. Stateful parameters share the same parameter settings as
|
|
14
|
+
* regular parameters except that they cannot be of type 'stateful'. See {@link ParameterSetting} for more
|
|
15
|
+
* information on available settings.
|
|
16
|
+
*
|
|
17
|
+
* @return The parameter settings
|
|
18
|
+
*/
|
|
19
|
+
getSettings() {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A specialized version of {@link StatefulParameter } that is used for stateful parameters which are arrays.
|
|
25
|
+
* A stateful parameter represents a parameter that holds state on the system (can be created, destroyed) but
|
|
26
|
+
* is still tied to the overall lifecycle of a resource.
|
|
27
|
+
*
|
|
28
|
+
* **Examples:**
|
|
29
|
+
* - Homebrew formulas are arrays
|
|
30
|
+
* - Pyenv python versions are arrays
|
|
31
|
+
*/
|
|
32
|
+
export class ArrayStatefulParameter {
|
|
33
|
+
/**
|
|
34
|
+
* Parameter settings for the stateful parameter. Stateful parameters share the same parameter settings as
|
|
35
|
+
* regular parameters except that they cannot be of type 'stateful'. See {@link ParameterSetting} for more
|
|
36
|
+
* information on available settings. Type must be 'array'.
|
|
37
|
+
*
|
|
38
|
+
* @return The parameter settings
|
|
39
|
+
*/
|
|
40
|
+
getSettings() {
|
|
41
|
+
return { type: 'array' };
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
3
3
|
import { SpawnOptions } from 'node:child_process';
|
|
4
|
-
import { ArrayParameterSetting } from '../resource/resource-settings.js';
|
|
5
4
|
export declare enum SpawnStatus {
|
|
6
5
|
SUCCESS = "success",
|
|
7
6
|
ERROR = "error"
|
|
@@ -37,5 +36,5 @@ export declare function splitUserConfig<T extends StringIndexedObject>(config: R
|
|
|
37
36
|
};
|
|
38
37
|
export declare function setsEqual(set1: Set<unknown>, set2: Set<unknown>): boolean;
|
|
39
38
|
export declare function untildify(pathWithTilde: string): string;
|
|
40
|
-
export declare function areArraysEqual(
|
|
39
|
+
export declare function areArraysEqual(isElementEqual: ((desired: unknown, current: unknown) => boolean) | undefined, desired: unknown, current: unknown): boolean;
|
|
41
40
|
export {};
|
package/dist/utils/utils.js
CHANGED
|
@@ -72,7 +72,7 @@ const homeDirectory = os.homedir();
|
|
|
72
72
|
export function untildify(pathWithTilde) {
|
|
73
73
|
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
|
|
74
74
|
}
|
|
75
|
-
export function areArraysEqual(
|
|
75
|
+
export function areArraysEqual(isElementEqual, desired, current) {
|
|
76
76
|
if (!Array.isArray(desired) || !Array.isArray(current)) {
|
|
77
77
|
throw new Error(`A non-array value:
|
|
78
78
|
|
|
@@ -91,7 +91,8 @@ Was provided even though type array was specified.
|
|
|
91
91
|
// Algorithm for to check equality between two un-ordered; un-hashable arrays using
|
|
92
92
|
// an isElementEqual method. Time: O(n^2)
|
|
93
93
|
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
|
|
94
|
-
const idx = currentCopy.findIndex((e2) => (
|
|
94
|
+
const idx = currentCopy.findIndex((e2) => (isElementEqual
|
|
95
|
+
?? ((a, b) => a === b))(desiredCopy[counter], e2));
|
|
95
96
|
if (idx === -1) {
|
|
96
97
|
return false;
|
|
97
98
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codify-plugin-lib",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.102",
|
|
4
4
|
"description": "Library plugin library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"ajv-formats": "^2.1.1",
|
|
17
17
|
"codify-schemas": "1.0.52",
|
|
18
18
|
"@npmcli/promise-spawn": "^7.0.1",
|
|
19
|
-
"uuid": "^10.0.0"
|
|
19
|
+
"uuid": "^10.0.0",
|
|
20
|
+
"lodash.isequal": "^4.5.0"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@oclif/prettier-config": "^0.2.1",
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"@types/semver": "^7.5.4",
|
|
27
28
|
"@types/sinon": "^17.0.3",
|
|
28
29
|
"@types/uuid": "^10.0.0",
|
|
30
|
+
"@types/lodash.isequal": "^4.5.8",
|
|
29
31
|
"chai-as-promised": "^7.1.1",
|
|
30
32
|
"vitest": "^1.4.0",
|
|
31
33
|
"vitest-mock-extended": "^1.3.1",
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ export * from './plugin/plugin.js'
|
|
|
9
9
|
export * from './resource/parsed-resource-settings.js';
|
|
10
10
|
export * from './resource/resource.js'
|
|
11
11
|
export * from './resource/resource-settings.js'
|
|
12
|
-
export * from './
|
|
12
|
+
export * from './stateful-parameter/stateful-parameter.js'
|
|
13
13
|
export * from './utils/utils.js'
|
|
14
14
|
|
|
15
15
|
export async function runPlugin(plugin: Plugin) {
|