codify-plugin-lib 1.0.94 → 1.0.96
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/plan/change-set.js +17 -24
- package/dist/plugin/plugin.js +3 -3
- package/dist/resource/parsed-resource-settings.js +24 -3
- package/dist/resource/resource-settings.d.ts +2 -1
- package/dist/resource/resource-settings.js +9 -1
- package/package.json +1 -1
- package/src/plan/change-set.ts +19 -27
- package/src/plugin/plugin.test.ts +77 -0
- package/src/plugin/plugin.ts +5 -5
- package/src/resource/parsed-resource-settings.test.ts +44 -0
- package/src/resource/parsed-resource-settings.ts +40 -3
- package/src/resource/resource-settings.test.ts +72 -0
- package/src/resource/resource-settings.ts +14 -2
package/dist/plan/change-set.js
CHANGED
|
@@ -81,46 +81,39 @@ export class ChangeSet {
|
|
|
81
81
|
// Filter out null and undefined values or else the diff below will not work
|
|
82
82
|
const desired = Object.fromEntries(Object.entries(desiredParameters).filter(([, v]) => v !== null && v !== undefined));
|
|
83
83
|
const current = Object.fromEntries(Object.entries(currentParameters).filter(([, v]) => v !== null && v !== undefined));
|
|
84
|
-
for (const
|
|
85
|
-
if (desired
|
|
84
|
+
for (const k of new Set([...Object.keys(current), ...Object.keys(desired)])) {
|
|
85
|
+
if (ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
|
|
86
86
|
parameterChangeSet.push({
|
|
87
87
|
name: k,
|
|
88
|
-
previousValue:
|
|
88
|
+
previousValue: current[k] ?? null,
|
|
89
|
+
newValue: desired[k] ?? null,
|
|
90
|
+
operation: ParameterOperation.NOOP,
|
|
91
|
+
});
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if ((desired?.[k] === null || desired?.[k] === undefined) && (current?.[k] !== null && current?.[k] !== undefined)) {
|
|
95
|
+
parameterChangeSet.push({
|
|
96
|
+
name: k,
|
|
97
|
+
previousValue: current[k] ?? null,
|
|
89
98
|
newValue: null,
|
|
90
99
|
operation: ParameterOperation.REMOVE,
|
|
91
100
|
});
|
|
92
|
-
delete current[k];
|
|
93
101
|
continue;
|
|
94
102
|
}
|
|
95
|
-
if (
|
|
103
|
+
if ((current?.[k] === null || current?.[k] === undefined) && (desired?.[k] !== null && desired?.[k] !== undefined)) {
|
|
96
104
|
parameterChangeSet.push({
|
|
97
105
|
name: k,
|
|
98
|
-
previousValue:
|
|
106
|
+
previousValue: null,
|
|
99
107
|
newValue: desired[k] ?? null,
|
|
100
|
-
operation: ParameterOperation.
|
|
108
|
+
operation: ParameterOperation.ADD,
|
|
101
109
|
});
|
|
102
|
-
delete current[k];
|
|
103
|
-
delete desired[k];
|
|
104
110
|
continue;
|
|
105
111
|
}
|
|
106
112
|
parameterChangeSet.push({
|
|
107
113
|
name: k,
|
|
108
|
-
previousValue:
|
|
114
|
+
previousValue: current[k] ?? null,
|
|
109
115
|
newValue: desired[k] ?? null,
|
|
110
|
-
operation: ParameterOperation.
|
|
111
|
-
});
|
|
112
|
-
delete current[k];
|
|
113
|
-
delete desired[k];
|
|
114
|
-
}
|
|
115
|
-
if (Object.keys(current).length > 0) {
|
|
116
|
-
throw new Error('Diff algorithm error');
|
|
117
|
-
}
|
|
118
|
-
for (const [k, v] of Object.entries(desired)) {
|
|
119
|
-
parameterChangeSet.push({
|
|
120
|
-
name: k,
|
|
121
|
-
previousValue: null,
|
|
122
|
-
newValue: v ?? null,
|
|
123
|
-
operation: ParameterOperation.ADD,
|
|
116
|
+
operation: ParameterOperation.MODIFY,
|
|
124
117
|
});
|
|
125
118
|
}
|
|
126
119
|
return parameterChangeSet;
|
package/dist/plugin/plugin.js
CHANGED
|
@@ -34,16 +34,16 @@ export class Plugin {
|
|
|
34
34
|
const resource = this.resourceControllers.get(data.type);
|
|
35
35
|
const schema = resource.settings.schema;
|
|
36
36
|
const requiredPropertyNames = (resource.settings.import?.requiredParameters
|
|
37
|
-
?? schema
|
|
37
|
+
?? schema?.required
|
|
38
38
|
?? null);
|
|
39
39
|
return {
|
|
40
40
|
plugin: this.name,
|
|
41
41
|
type: data.type,
|
|
42
42
|
dependencies: resource.dependencies,
|
|
43
43
|
schema: schema,
|
|
44
|
-
import:
|
|
44
|
+
import: {
|
|
45
45
|
requiredParameters: requiredPropertyNames,
|
|
46
|
-
}
|
|
46
|
+
},
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
async import(data) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveEqualsFn } from './resource-settings.js';
|
|
1
|
+
import { resolveEqualsFn, resolveParameterTransformFn } from './resource-settings.js';
|
|
2
2
|
export class ParsedResourceSettings {
|
|
3
3
|
cache = new Map();
|
|
4
4
|
id;
|
|
@@ -61,8 +61,8 @@ export class ParsedResourceSettings {
|
|
|
61
61
|
return {};
|
|
62
62
|
}
|
|
63
63
|
return Object.fromEntries(Object.entries(this.settings.parameterSettings)
|
|
64
|
-
.filter(([, v]) => v
|
|
65
|
-
.map(([k, v]) => [k, v
|
|
64
|
+
.filter(([, v]) => resolveParameterTransformFn(v) !== undefined)
|
|
65
|
+
.map(([k, v]) => [k, resolveParameterTransformFn(v)]));
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
get statefulParameterOrder() {
|
|
@@ -94,6 +94,27 @@ export class ParsedResourceSettings {
|
|
|
94
94
|
&& Object.values(this.parameterSettings).some((v) => v.type === 'stateful')) {
|
|
95
95
|
throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed if multiples of a resource exist`);
|
|
96
96
|
}
|
|
97
|
+
const schema = this.settings.schema;
|
|
98
|
+
if (!this.settings.import && (schema?.oneOf
|
|
99
|
+
&& Array.isArray(schema.oneOf)
|
|
100
|
+
&& schema.oneOf.some((s) => s.required))
|
|
101
|
+
|| (schema?.anyOf
|
|
102
|
+
&& Array.isArray(schema.anyOf)
|
|
103
|
+
&& schema.anyOf.some((s) => s.required))
|
|
104
|
+
|| (schema?.allOf
|
|
105
|
+
&& Array.isArray(schema.allOf)
|
|
106
|
+
&& schema.allOf.some((s) => s.required))
|
|
107
|
+
|| (schema?.then
|
|
108
|
+
&& Array.isArray(schema.then)
|
|
109
|
+
&& schema.then.some((s) => s.required))
|
|
110
|
+
|| (schema?.else
|
|
111
|
+
&& Array.isArray(schema.else)
|
|
112
|
+
&& schema.else.some((s) => s.required))) {
|
|
113
|
+
throw new Error(`In the schema of ${this.settings.id}, a conditional required was declared (within anyOf, allOf, oneOf, else, or then) but an` +
|
|
114
|
+
'import.requiredParameters was not found in the resource settings. This is required because Codify uses the required parameter to' +
|
|
115
|
+
'determine the prompt to ask users during imports. It can\'t parse which parameters are needed when ' +
|
|
116
|
+
'required is declared conditionally.');
|
|
117
|
+
}
|
|
97
118
|
}
|
|
98
119
|
validateParameterEqualsFn(parameter, key) {
|
|
99
120
|
if (parameter.type === 'stateful') {
|
|
@@ -61,7 +61,7 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
61
61
|
* config with desired config. Certain types will have additional options to help support it. For example the type
|
|
62
62
|
* stateful requires a stateful parameter definition and type array takes an isElementEqual method.
|
|
63
63
|
*/
|
|
64
|
-
export type ParameterSettingType = 'any' | 'array' | 'boolean' | 'directory' | 'number' | 'stateful' | 'string' | 'version';
|
|
64
|
+
export type ParameterSettingType = 'any' | 'array' | 'boolean' | 'directory' | 'number' | 'setting' | 'stateful' | 'string' | 'version';
|
|
65
65
|
/**
|
|
66
66
|
* Typing information for the parameter setting. This represents a setting on a specific parameter within a
|
|
67
67
|
* resource. Options for configuring parameters operations including overriding the equals function, adding default values
|
|
@@ -150,3 +150,4 @@ export interface StatefulParameterSetting extends DefaultParameterSetting {
|
|
|
150
150
|
order?: number;
|
|
151
151
|
}
|
|
152
152
|
export declare function resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) => boolean;
|
|
153
|
+
export declare function resolveParameterTransformFn(parameter: ParameterSetting): ((input: any) => Promise<any> | any) | undefined;
|
|
@@ -5,7 +5,8 @@ const ParameterEqualsDefaults = {
|
|
|
5
5
|
'directory': (a, b) => path.resolve(untildify(String(a))) === path.resolve(untildify(String(b))),
|
|
6
6
|
'number': (a, b) => Number(a) === Number(b),
|
|
7
7
|
'string': (a, b) => String(a) === String(b),
|
|
8
|
-
'version': (desired, current) => String(current).includes(String(desired))
|
|
8
|
+
'version': (desired, current) => String(current).includes(String(desired)),
|
|
9
|
+
'setting': (a, b) => true,
|
|
9
10
|
};
|
|
10
11
|
export function resolveEqualsFn(parameter, key) {
|
|
11
12
|
if (parameter.type === 'array') {
|
|
@@ -16,3 +17,10 @@ export function resolveEqualsFn(parameter, key) {
|
|
|
16
17
|
}
|
|
17
18
|
return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type] ?? (((a, b) => a === b));
|
|
18
19
|
}
|
|
20
|
+
const ParameterTransformationDefaults = {
|
|
21
|
+
'directory': (a) => path.resolve(untildify(String(a))),
|
|
22
|
+
'string': String,
|
|
23
|
+
};
|
|
24
|
+
export function resolveParameterTransformFn(parameter) {
|
|
25
|
+
return parameter.inputTransformation ?? ParameterTransformationDefaults[parameter.type] ?? undefined;
|
|
26
|
+
}
|
package/package.json
CHANGED
package/src/plan/change-set.ts
CHANGED
|
@@ -141,53 +141,45 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
141
141
|
Object.entries(currentParameters).filter(([, v]) => v !== null && v !== undefined)
|
|
142
142
|
) as Partial<T>
|
|
143
143
|
|
|
144
|
-
for (const
|
|
145
|
-
if (desired
|
|
144
|
+
for (const k of new Set([...Object.keys(current), ...Object.keys(desired)])) {
|
|
145
|
+
if (ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
|
|
146
146
|
parameterChangeSet.push({
|
|
147
147
|
name: k,
|
|
148
|
-
previousValue:
|
|
148
|
+
previousValue: current[k] ?? null,
|
|
149
|
+
newValue: desired[k] ?? null,
|
|
150
|
+
operation: ParameterOperation.NOOP,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if ((desired?.[k] === null || desired?.[k] === undefined) && (current?.[k] !== null && current?.[k] !== undefined)) {
|
|
157
|
+
parameterChangeSet.push({
|
|
158
|
+
name: k,
|
|
159
|
+
previousValue: current[k] ?? null,
|
|
149
160
|
newValue: null,
|
|
150
161
|
operation: ParameterOperation.REMOVE,
|
|
151
162
|
})
|
|
152
163
|
|
|
153
|
-
delete current[k];
|
|
154
164
|
continue;
|
|
155
165
|
}
|
|
156
166
|
|
|
157
|
-
if (
|
|
167
|
+
if ((current?.[k] === null || current?.[k] === undefined) && (desired?.[k] !== null && desired?.[k] !== undefined)) {
|
|
158
168
|
parameterChangeSet.push({
|
|
159
169
|
name: k,
|
|
160
|
-
previousValue:
|
|
170
|
+
previousValue: null,
|
|
161
171
|
newValue: desired[k] ?? null,
|
|
162
|
-
operation: ParameterOperation.
|
|
172
|
+
operation: ParameterOperation.ADD,
|
|
163
173
|
})
|
|
164
174
|
|
|
165
|
-
delete current[k];
|
|
166
|
-
delete desired[k];
|
|
167
175
|
continue;
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
parameterChangeSet.push({
|
|
171
179
|
name: k,
|
|
172
|
-
previousValue:
|
|
180
|
+
previousValue: current[k] ?? null,
|
|
173
181
|
newValue: desired[k] ?? null,
|
|
174
|
-
operation: ParameterOperation.
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
delete current[k];
|
|
178
|
-
delete desired[k];
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (Object.keys(current).length > 0) {
|
|
182
|
-
throw new Error('Diff algorithm error');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
for (const [k, v] of Object.entries(desired)) {
|
|
186
|
-
parameterChangeSet.push({
|
|
187
|
-
name: k,
|
|
188
|
-
previousValue: null,
|
|
189
|
-
newValue: v ?? null,
|
|
190
|
-
operation: ParameterOperation.ADD,
|
|
182
|
+
operation: ParameterOperation.MODIFY,
|
|
191
183
|
})
|
|
192
184
|
}
|
|
193
185
|
|
|
@@ -101,4 +101,81 @@ describe('Plugin tests', () => {
|
|
|
101
101
|
await testPlugin.apply({ plan })
|
|
102
102
|
expect(resource.modify.calledOnce).to.be.true;
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
it('Can get resource info', async () => {
|
|
106
|
+
const schema = {
|
|
107
|
+
'$schema': 'http://json-schema.org/draft-07/schema',
|
|
108
|
+
'$id': 'https://www.codifycli.com/asdf-schema.json',
|
|
109
|
+
'title': 'Asdf resource',
|
|
110
|
+
'type': 'object',
|
|
111
|
+
'properties': {
|
|
112
|
+
'plugins': {
|
|
113
|
+
'type': 'array',
|
|
114
|
+
'description': 'Asdf plugins to install. See: https://github.com/asdf-community for a full list',
|
|
115
|
+
'items': {
|
|
116
|
+
'type': 'string'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
'required': ['plugins'],
|
|
121
|
+
'additionalProperties': false
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
const resource = new class extends TestResource {
|
|
126
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
127
|
+
return {
|
|
128
|
+
id: 'typeId',
|
|
129
|
+
schema,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const testPlugin = Plugin.create('testPlugin', [resource as any])
|
|
134
|
+
|
|
135
|
+
const resourceInfo = await testPlugin.getResourceInfo({ type: 'typeId' })
|
|
136
|
+
expect(resourceInfo.import).toMatchObject({
|
|
137
|
+
requiredParameters: [
|
|
138
|
+
'plugins'
|
|
139
|
+
]
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('Get resource info to default import to the one specified in the resource settings', async () => {
|
|
144
|
+
const schema = {
|
|
145
|
+
'$schema': 'http://json-schema.org/draft-07/schema',
|
|
146
|
+
'$id': 'https://www.codifycli.com/asdf-schema.json',
|
|
147
|
+
'title': 'Asdf resource',
|
|
148
|
+
'type': 'object',
|
|
149
|
+
'properties': {
|
|
150
|
+
'plugins': {
|
|
151
|
+
'type': 'array',
|
|
152
|
+
'description': 'Asdf plugins to install. See: https://github.com/asdf-community for a full list',
|
|
153
|
+
'items': {
|
|
154
|
+
'type': 'string'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
'required': ['plugins'],
|
|
159
|
+
'additionalProperties': false
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
const resource = new class extends TestResource {
|
|
164
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
165
|
+
return {
|
|
166
|
+
id: 'typeId',
|
|
167
|
+
schema,
|
|
168
|
+
import: {
|
|
169
|
+
requiredParameters: []
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const testPlugin = Plugin.create('testPlugin', [resource as any])
|
|
175
|
+
|
|
176
|
+
const resourceInfo = await testPlugin.getResourceInfo({ type: 'typeId' })
|
|
177
|
+
expect(resourceInfo.import).toMatchObject({
|
|
178
|
+
requiredParameters: []
|
|
179
|
+
})
|
|
180
|
+
})
|
|
104
181
|
});
|
package/src/plugin/plugin.ts
CHANGED
|
@@ -59,11 +59,11 @@ export class Plugin {
|
|
|
59
59
|
|
|
60
60
|
const resource = this.resourceControllers.get(data.type)!;
|
|
61
61
|
|
|
62
|
-
const schema = resource.settings.schema as JSONSchemaType<any
|
|
62
|
+
const schema = resource.settings.schema as JSONSchemaType<any> | undefined;
|
|
63
63
|
const requiredPropertyNames = (
|
|
64
64
|
resource.settings.import?.requiredParameters
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
?? schema?.required
|
|
66
|
+
?? null
|
|
67
67
|
) as null | string[];
|
|
68
68
|
|
|
69
69
|
return {
|
|
@@ -71,9 +71,9 @@ export class Plugin {
|
|
|
71
71
|
type: data.type,
|
|
72
72
|
dependencies: resource.dependencies,
|
|
73
73
|
schema: schema as Record<string, unknown> | undefined,
|
|
74
|
-
import:
|
|
74
|
+
import: {
|
|
75
75
|
requiredParameters: requiredPropertyNames,
|
|
76
|
-
}
|
|
76
|
+
},
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -21,4 +21,48 @@ describe('Resource options parser tests', () => {
|
|
|
21
21
|
propB: 'propB'
|
|
22
22
|
})
|
|
23
23
|
})
|
|
24
|
+
|
|
25
|
+
it('Throws an error when an import.requiredParameters is not declared', () => {
|
|
26
|
+
const schema = {
|
|
27
|
+
'$schema': 'http://json-schema.org/draft-07/schema',
|
|
28
|
+
'$id': 'https://www.codifycli.com/git-clone.json',
|
|
29
|
+
'title': 'Git-clone resource',
|
|
30
|
+
'type': 'object',
|
|
31
|
+
'properties': {
|
|
32
|
+
'remote': {
|
|
33
|
+
'type': 'string',
|
|
34
|
+
'description': 'Remote tracking url to clone repo from. Equivalent to repository and only one should be specified'
|
|
35
|
+
},
|
|
36
|
+
'repository': {
|
|
37
|
+
'type': 'string',
|
|
38
|
+
'description': 'Remote repository to clone repo from. Equivalent to remote and only one should be specified'
|
|
39
|
+
},
|
|
40
|
+
'parentDirectory': {
|
|
41
|
+
'type': 'string',
|
|
42
|
+
'description': 'Parent directory to clone into. The folder name will use default git semantics which extracts the last part of the clone url. Only one of parentDirectory or directory can be specified'
|
|
43
|
+
},
|
|
44
|
+
'directory': {
|
|
45
|
+
'type': 'string',
|
|
46
|
+
'description': 'Directory to clone contents into. This value is directly passed into git clone. This differs from parent directory in that the last part of the path will be the folder name of the repo'
|
|
47
|
+
},
|
|
48
|
+
'autoVerifySSH': {
|
|
49
|
+
'type': 'boolean',
|
|
50
|
+
'description': 'Automatically verifies the ssh connection for ssh git clones. Defaults to true.'
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
'additionalProperties': false,
|
|
54
|
+
'oneOf': [
|
|
55
|
+
{ 'required': ['repository', 'directory'] },
|
|
56
|
+
{ 'required': ['repository', 'parentDirectory'] },
|
|
57
|
+
{ 'required': ['remote', 'directory'] }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const option: ResourceSettings<TestConfig> = {
|
|
62
|
+
id: 'typeId',
|
|
63
|
+
schema,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
expect(() => new ParsedResourceSettings(option)).throws;
|
|
67
|
+
})
|
|
24
68
|
})
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { JSONSchemaType } from 'ajv';
|
|
1
2
|
import { StringIndexedObject } from 'codify-schemas';
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ParameterSetting,
|
|
6
|
+
ResourceSettings,
|
|
7
|
+
StatefulParameterSetting,
|
|
8
|
+
resolveEqualsFn,
|
|
9
|
+
resolveParameterTransformFn
|
|
10
|
+
} from './resource-settings.js';
|
|
4
11
|
import { StatefulParameter as StatefulParameterImpl } from './stateful-parameter.js'
|
|
5
12
|
|
|
6
13
|
export class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
|
|
@@ -87,8 +94,8 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
87
94
|
|
|
88
95
|
return Object.fromEntries(
|
|
89
96
|
Object.entries(this.settings.parameterSettings)
|
|
90
|
-
.filter(([, v]) => v
|
|
91
|
-
.map(([k, v]) => [k, v
|
|
97
|
+
.filter(([, v]) => resolveParameterTransformFn(v!) !== undefined)
|
|
98
|
+
.map(([k, v]) => [k, resolveParameterTransformFn(v!)] as const)
|
|
92
99
|
) as Record<keyof T, (a: unknown) => unknown>;
|
|
93
100
|
});
|
|
94
101
|
}
|
|
@@ -130,6 +137,36 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
130
137
|
&& Object.values(this.parameterSettings).some((v) => v.type === 'stateful')) {
|
|
131
138
|
throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed if multiples of a resource exist`)
|
|
132
139
|
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
const schema = this.settings.schema as JSONSchemaType<any>;
|
|
143
|
+
if (!this.settings.import && (schema?.oneOf
|
|
144
|
+
&& Array.isArray(schema.oneOf)
|
|
145
|
+
&& schema.oneOf.some((s) => s.required)
|
|
146
|
+
)
|
|
147
|
+
|| (schema?.anyOf
|
|
148
|
+
&& Array.isArray(schema.anyOf)
|
|
149
|
+
&& schema.anyOf.some((s) => s.required)
|
|
150
|
+
)
|
|
151
|
+
|| (schema?.allOf
|
|
152
|
+
&& Array.isArray(schema.allOf)
|
|
153
|
+
&& schema.allOf.some((s) => s.required)
|
|
154
|
+
)
|
|
155
|
+
|| (schema?.then
|
|
156
|
+
&& Array.isArray(schema.then)
|
|
157
|
+
&& schema.then.some((s) => s.required)
|
|
158
|
+
)
|
|
159
|
+
|| (schema?.else
|
|
160
|
+
&& Array.isArray(schema.else)
|
|
161
|
+
&& schema.else.some((s) => s.required)
|
|
162
|
+
)
|
|
163
|
+
) {
|
|
164
|
+
throw new Error(`In the schema of ${this.settings.id}, a conditional required was declared (within anyOf, allOf, oneOf, else, or then) but an` +
|
|
165
|
+
'import.requiredParameters was not found in the resource settings. This is required because Codify uses the required parameter to' +
|
|
166
|
+
'determine the prompt to ask users during imports. It can\'t parse which parameters are needed when ' +
|
|
167
|
+
'required is declared conditionally.'
|
|
168
|
+
)
|
|
169
|
+
}
|
|
133
170
|
}
|
|
134
171
|
|
|
135
172
|
private validateParameterEqualsFn(parameter: ParameterSetting, key: string): void {
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
} from '../utils/test-utils.test.js';
|
|
12
12
|
import { ArrayParameterSetting, ParameterSetting, ResourceSettings } from './resource-settings.js';
|
|
13
13
|
import { ResourceController } from './resource-controller.js';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import path from 'node:path';
|
|
14
16
|
|
|
15
17
|
describe('Resource parameter tests', () => {
|
|
16
18
|
it('Generates a resource plan that includes stateful parameters (create)', async () => {
|
|
@@ -540,4 +542,74 @@ describe('Resource parameter tests', () => {
|
|
|
540
542
|
}
|
|
541
543
|
};
|
|
542
544
|
})
|
|
545
|
+
|
|
546
|
+
it('Applies default input transformations', async () => {
|
|
547
|
+
const home = os.homedir()
|
|
548
|
+
const testPath = path.join(home, 'test/folder');
|
|
549
|
+
|
|
550
|
+
const resource = new class extends TestResource {
|
|
551
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
552
|
+
return {
|
|
553
|
+
id: 'resourceType',
|
|
554
|
+
parameterSettings: {
|
|
555
|
+
propA: { type: 'directory' }
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
561
|
+
return { propA: testPath }
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const controller = new ResourceController(resource);
|
|
566
|
+
const plan = await controller.plan({ type: 'resourceType', propA: '~/test/folder' } as any);
|
|
567
|
+
|
|
568
|
+
expect(plan.changeSet.parameterChanges[0]).toMatchObject({
|
|
569
|
+
operation: ParameterOperation.NOOP,
|
|
570
|
+
newValue: testPath,
|
|
571
|
+
previousValue: testPath,
|
|
572
|
+
})
|
|
573
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
it('Ignores setting parameters when planning', async () => {
|
|
577
|
+
const resource = new class extends TestResource {
|
|
578
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
579
|
+
return {
|
|
580
|
+
id: 'resourceType',
|
|
581
|
+
parameterSettings: {
|
|
582
|
+
propA: { type: 'setting' },
|
|
583
|
+
propB: { type: 'number' }
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
589
|
+
return { propB: 64 }
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const controller = new ResourceController(resource);
|
|
594
|
+
const plan = await controller.plan({ type: 'resourceType', propA: 'setting', propB: 64 } as any);
|
|
595
|
+
|
|
596
|
+
expect(plan.changeSet.parameterChanges).toMatchObject(
|
|
597
|
+
expect.arrayContaining([
|
|
598
|
+
{
|
|
599
|
+
name: 'propA',
|
|
600
|
+
operation: ParameterOperation.NOOP,
|
|
601
|
+
previousValue: null,
|
|
602
|
+
newValue: 'setting',
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
name: 'propB',
|
|
606
|
+
operation: ParameterOperation.NOOP,
|
|
607
|
+
previousValue: 64,
|
|
608
|
+
newValue: 64,
|
|
609
|
+
}
|
|
610
|
+
])
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
614
|
+
})
|
|
543
615
|
})
|
|
@@ -81,6 +81,7 @@ export type ParameterSettingType =
|
|
|
81
81
|
| 'boolean'
|
|
82
82
|
| 'directory'
|
|
83
83
|
| 'number'
|
|
84
|
+
| 'setting'
|
|
84
85
|
| 'stateful'
|
|
85
86
|
| 'string'
|
|
86
87
|
| 'version';
|
|
@@ -192,10 +193,10 @@ const ParameterEqualsDefaults: Partial<Record<ParameterSettingType, (a: unknown,
|
|
|
192
193
|
'directory': (a: unknown, b: unknown) => path.resolve(untildify(String(a))) === path.resolve(untildify(String(b))),
|
|
193
194
|
'number': (a: unknown, b: unknown) => Number(a) === Number(b),
|
|
194
195
|
'string': (a: unknown, b: unknown) => String(a) === String(b),
|
|
195
|
-
'version': (desired: unknown, current: unknown) => String(current).includes(String(desired))
|
|
196
|
+
'version': (desired: unknown, current: unknown) => String(current).includes(String(desired)),
|
|
197
|
+
'setting': (a: unknown, b: unknown) => true,
|
|
196
198
|
}
|
|
197
199
|
|
|
198
|
-
|
|
199
200
|
export function resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) => boolean {
|
|
200
201
|
if (parameter.type === 'array') {
|
|
201
202
|
return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter as ArrayParameterSetting)
|
|
@@ -207,3 +208,14 @@ export function resolveEqualsFn(parameter: ParameterSetting, key: string): (desi
|
|
|
207
208
|
|
|
208
209
|
return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type as ParameterSettingType] ?? (((a, b) => a === b));
|
|
209
210
|
}
|
|
211
|
+
|
|
212
|
+
const ParameterTransformationDefaults: Partial<Record<ParameterSettingType, (input: any) => Promise<any> | any>> = {
|
|
213
|
+
'directory': (a: unknown) => path.resolve(untildify(String(a))),
|
|
214
|
+
'string': String,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function resolveParameterTransformFn(
|
|
218
|
+
parameter: ParameterSetting
|
|
219
|
+
): ((input: any) => Promise<any> | any) | undefined {
|
|
220
|
+
return parameter.inputTransformation ?? ParameterTransformationDefaults[parameter.type as ParameterSettingType] ?? undefined;
|
|
221
|
+
}
|