codify-plugin-lib 1.0.182-beta7 → 1.0.182-beta71

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 (53) hide show
  1. package/bin/build.js +189 -0
  2. package/dist/bin/build.js +0 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +3 -0
  5. package/dist/messages/handlers.js +10 -2
  6. package/dist/plan/plan.js +45 -3
  7. package/dist/plugin/plugin.d.ts +2 -1
  8. package/dist/plugin/plugin.js +6 -1
  9. package/dist/pty/background-pty.d.ts +3 -2
  10. package/dist/pty/background-pty.js +7 -14
  11. package/dist/pty/index.d.ts +8 -2
  12. package/dist/pty/seqeuntial-pty.d.ts +3 -2
  13. package/dist/pty/seqeuntial-pty.js +47 -12
  14. package/dist/resource/parsed-resource-settings.d.ts +5 -2
  15. package/dist/resource/parsed-resource-settings.js +16 -2
  16. package/dist/resource/resource-controller.js +5 -5
  17. package/dist/resource/resource-settings.d.ts +13 -3
  18. package/dist/resource/resource-settings.js +2 -2
  19. package/dist/test.d.ts +1 -0
  20. package/dist/test.js +5 -0
  21. package/dist/utils/file-utils.d.ts +14 -7
  22. package/dist/utils/file-utils.js +65 -51
  23. package/dist/utils/functions.js +2 -2
  24. package/dist/utils/index.d.ts +21 -1
  25. package/dist/utils/index.js +160 -0
  26. package/dist/utils/load-resources.d.ts +1 -0
  27. package/dist/utils/load-resources.js +46 -0
  28. package/dist/utils/package-json-utils.d.ts +12 -0
  29. package/dist/utils/package-json-utils.js +34 -0
  30. package/package.json +5 -4
  31. package/rollup.config.js +24 -0
  32. package/src/index.ts +3 -0
  33. package/src/messages/handlers.test.ts +23 -0
  34. package/src/messages/handlers.ts +11 -2
  35. package/src/plan/plan.test.ts +46 -0
  36. package/src/plan/plan.ts +65 -4
  37. package/src/plugin/plugin.test.ts +31 -0
  38. package/src/plugin/plugin.ts +8 -2
  39. package/src/pty/background-pty.ts +10 -18
  40. package/src/pty/index.ts +10 -4
  41. package/src/pty/seqeuntial-pty.ts +62 -16
  42. package/src/pty/sequential-pty.test.ts +137 -4
  43. package/src/resource/parsed-resource-settings.test.ts +24 -0
  44. package/src/resource/parsed-resource-settings.ts +26 -8
  45. package/src/resource/resource-controller.test.ts +126 -0
  46. package/src/resource/resource-controller.ts +5 -6
  47. package/src/resource/resource-settings.test.ts +36 -0
  48. package/src/resource/resource-settings.ts +17 -5
  49. package/src/utils/file-utils.test.ts +7 -0
  50. package/src/utils/file-utils.ts +70 -55
  51. package/src/utils/functions.ts +3 -3
  52. package/src/utils/index.ts +197 -1
  53. package/src/utils/internal-utils.test.ts +1 -0
@@ -1,6 +1,8 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { SequentialPty } from './seqeuntial-pty.js';
3
3
  import { VerbosityLevel } from '../utils/verbosity-level.js';
4
+ import { MessageStatus, SpawnStatus } from 'codify-schemas/src/types/index.js';
5
+ import { IpcMessageV2, MessageCmd } from 'codify-schemas';
4
6
 
5
7
  describe('SequentialPty tests', () => {
6
8
  it('Can launch a simple command', async () => {
@@ -33,8 +35,8 @@ describe('SequentialPty tests', () => {
33
35
  const resultFailed = await pty.spawnSafe('which sjkdhsakjdhjkash');
34
36
  expect(resultFailed).toMatchObject({
35
37
  status: 'error',
36
- exitCode: 127,
37
- data: 'zsh:1: command not found: which sjkdhsakjdhjkash' // This might change on different os or shells. Keep for now.
38
+ exitCode: 1,
39
+ data: 'sjkdhsakjdhjkash not found' // This might change on different os or shells. Keep for now.
38
40
  })
39
41
  });
40
42
 
@@ -49,13 +51,144 @@ describe('SequentialPty tests', () => {
49
51
  })
50
52
  });
51
53
 
52
- it('It can launch a command in interactive mode', async () => {
54
+
55
+ it('Can use multi-line commands', async () => {
53
56
  const pty = new SequentialPty();
54
57
 
55
- const resultSuccess = await pty.spawnSafe('ls', { interactive: true });
58
+ const resultSuccess = await pty.spawnSafe([
59
+ 'pwd',
60
+ '&& ls',
61
+ ], { cwd: '/tmp' });
56
62
  expect(resultSuccess).toMatchObject({
57
63
  status: 'success',
58
64
  exitCode: 0,
59
65
  })
60
66
  });
67
+
68
+
69
+ it('It can launch a command in interactive mode', async () => {
70
+ const originalSend = process.send;
71
+ process.send = (req: IpcMessageV2) => {
72
+ expect(req).toMatchObject({
73
+ cmd: MessageCmd.COMMAND_REQUEST,
74
+ requestId: expect.any(String),
75
+ data: {
76
+ command: 'ls',
77
+ options: {
78
+ cwd: '/tmp',
79
+ interactive: true,
80
+ }
81
+ }
82
+ })
83
+
84
+ // This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
85
+ const listeners = process.listeners('message');
86
+ listeners[2](({
87
+ cmd: MessageCmd.COMMAND_REQUEST,
88
+ requestId: req.requestId,
89
+ status: MessageStatus.SUCCESS,
90
+ data: {
91
+ status: SpawnStatus.SUCCESS,
92
+ exitCode: 0,
93
+ data: 'My data',
94
+ }
95
+ }))
96
+
97
+ return true;
98
+ }
99
+
100
+ const $ = new SequentialPty();
101
+ const resultSuccess = await $.spawnSafe('ls', { interactive: true, cwd: '/tmp' });
102
+
103
+ expect(resultSuccess).toMatchObject({
104
+ status: 'success',
105
+ exitCode: 0,
106
+ });
107
+
108
+ process.send = originalSend;
109
+ });
110
+
111
+ it('It can work with root (sudo)', async () => {
112
+ const originalSend = process.send;
113
+ process.send = (req: IpcMessageV2) => {
114
+ expect(req).toMatchObject({
115
+ cmd: MessageCmd.COMMAND_REQUEST,
116
+ requestId: expect.any(String),
117
+ data: {
118
+ command: 'ls',
119
+ options: {
120
+ interactive: true,
121
+ requiresRoot: true,
122
+ }
123
+ }
124
+ })
125
+
126
+ // This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
127
+ const listeners = process.listeners('message');
128
+ listeners[2](({
129
+ cmd: MessageCmd.COMMAND_REQUEST,
130
+ requestId: req.requestId,
131
+ status: MessageStatus.SUCCESS,
132
+ data: {
133
+ status: SpawnStatus.SUCCESS,
134
+ exitCode: 0,
135
+ data: 'My data',
136
+ }
137
+ }))
138
+
139
+ return true;
140
+ }
141
+
142
+ const $ = new SequentialPty();
143
+ const resultSuccess = await $.spawn('ls', { interactive: true, requiresRoot: true });
144
+
145
+ expect(resultSuccess).toMatchObject({
146
+ status: 'success',
147
+ exitCode: 0,
148
+ });
149
+
150
+ process.send = originalSend;
151
+ })
152
+
153
+ it('It can handle errors when in sudo', async () => {
154
+ const originalSend = process.send;
155
+ process.send = (req: IpcMessageV2) => {
156
+ expect(req).toMatchObject({
157
+ cmd: MessageCmd.COMMAND_REQUEST,
158
+ requestId: expect.any(String),
159
+ data: {
160
+ command: 'ls',
161
+ options: {
162
+ requiresRoot: true,
163
+ interactive: true,
164
+ }
165
+ }
166
+ })
167
+
168
+ // This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
169
+ const listeners = process.listeners('message');
170
+ listeners[2](({
171
+ cmd: MessageCmd.COMMAND_REQUEST,
172
+ requestId: req.requestId,
173
+ status: MessageStatus.SUCCESS,
174
+ data: {
175
+ status: SpawnStatus.ERROR,
176
+ exitCode: 127,
177
+ data: 'My data',
178
+ }
179
+ }))
180
+
181
+ return true;
182
+ }
183
+
184
+ const $ = new SequentialPty();
185
+ const resultSuccess = await $.spawnSafe('ls', { interactive: true, requiresRoot: true });
186
+
187
+ expect(resultSuccess).toMatchObject({
188
+ status: SpawnStatus.ERROR,
189
+ exitCode: 127,
190
+ });
191
+
192
+ process.send = originalSend;
193
+ })
61
194
  })
@@ -2,6 +2,8 @@ import { describe, expect, it } from 'vitest';
2
2
  import { ResourceSettings } from './resource-settings.js';
3
3
  import { ParsedResourceSettings } from './parsed-resource-settings.js';
4
4
  import { TestConfig } from '../utils/test-utils.test.js';
5
+ import { z } from 'zod';
6
+ import { OS } from 'codify-schemas';
5
7
 
6
8
  describe('Resource options parser tests', () => {
7
9
  it('Parses default values from options', () => {
@@ -159,4 +161,26 @@ describe('Resource options parser tests', () => {
159
161
 
160
162
  expect(() => new ParsedResourceSettings(option)).toThrowError()
161
163
  })
164
+
165
+ it('Can handle a zod schema', () => {
166
+
167
+ const schema = z.object({
168
+ propA: z.string(),
169
+ repository: z.string(),
170
+ })
171
+
172
+ const option: ResourceSettings<z.infer<typeof schema>> = {
173
+ id: 'typeId',
174
+ operatingSystems: [OS.Darwin],
175
+ schema,
176
+ importAndDestroy: {
177
+ defaultRefreshValues: {
178
+ repository: 'abc'
179
+ }
180
+ }
181
+ }
182
+
183
+ console.log(new ParsedResourceSettings(option))
184
+
185
+ })
162
186
  })
@@ -1,5 +1,6 @@
1
1
  import { JSONSchemaType } from 'ajv';
2
- import { OS, StringIndexedObject } from 'codify-schemas';
2
+ import { LinuxDistro, OS, StringIndexedObject } from 'codify-schemas';
3
+ import { ZodObject, z } from 'zod';
3
4
 
4
5
  import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
5
6
  import {
@@ -7,12 +8,12 @@ import {
7
8
  DefaultParameterSetting,
8
9
  InputTransformation,
9
10
  ParameterSetting,
11
+ ResourceSettings,
12
+ StatefulParameterSetting,
10
13
  resolveElementEqualsFn,
11
14
  resolveEqualsFn,
12
15
  resolveMatcher,
13
- resolveParameterTransformFn,
14
- ResourceSettings,
15
- StatefulParameterSetting
16
+ resolveParameterTransformFn
16
17
  } from './resource-settings.js';
17
18
 
18
19
  export interface ParsedStatefulParameterSetting extends DefaultParameterSetting {
@@ -29,7 +30,7 @@ export type ParsedArrayParameterSetting = {
29
30
 
30
31
  export type ParsedParameterSetting =
31
32
  {
32
- isEqual: (desired: unknown, current: unknown) => boolean;
33
+ isEqual: (desired: unknown, current: unknown) => boolean;
33
34
  } & (DefaultParameterSetting
34
35
  | ParsedArrayParameterSetting
35
36
  | ParsedStatefulParameterSetting)
@@ -37,10 +38,13 @@ export type ParsedParameterSetting =
37
38
  export class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
38
39
  private cache = new Map<string, unknown>();
39
40
  id!: string;
41
+ description?: string;
42
+
40
43
  schema?: Partial<JSONSchemaType<T | any>>;
41
44
  allowMultiple?: {
45
+ identifyingParameters?: string[];
42
46
  matcher?: (desired: Partial<T>, current: Partial<T>) => boolean;
43
- requiredParameters?: string[]
47
+ findAllParameters?: () => Promise<Array<Partial<T>>>
44
48
  } | boolean;
45
49
 
46
50
  removeStatefulParametersBeforeDestroy?: boolean | undefined;
@@ -48,16 +52,30 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
48
52
  transformation?: InputTransformation;
49
53
 
50
54
  operatingSystems!: Array<OS>;
55
+ linuxDistros?: Array<LinuxDistro>;
56
+
51
57
  isSensitive?: boolean;
52
58
 
53
59
  private settings: ResourceSettings<T>;
54
60
 
55
61
  constructor(settings: ResourceSettings<T>) {
56
62
  this.settings = settings;
63
+ const { parameterSettings, schema, ...rest } = settings;
57
64
 
58
- const { parameterSettings, ...rest } = settings;
59
65
  Object.assign(this, rest);
60
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
+
61
79
  this.validateSettings();
62
80
  }
63
81
 
@@ -199,7 +217,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
199
217
  throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed to be identifying parameters for allowMultiple.`)
200
218
  }
201
219
 
202
- const schema = this.settings.schema as JSONSchemaType<any>;
220
+ const schema = this.schema as JSONSchemaType<any>;
203
221
  if (!this.settings.importAndDestroy && (schema?.oneOf
204
222
  && Array.isArray(schema.oneOf)
205
223
  && schema.oneOf.some((s) => s.required)
@@ -11,6 +11,7 @@ import { tildify, untildify } from '../utils/functions.js';
11
11
  import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
12
12
  import { Plan } from '../plan/plan.js';
13
13
  import os from 'node:os';
14
+ import { z } from 'zod';
14
15
 
15
16
  describe('Resource tests', () => {
16
17
 
@@ -952,4 +953,129 @@ describe('Resource tests', () => {
952
953
 
953
954
  process.env = oldProcessEnv;
954
955
  })
956
+
957
+ it('Can import and return all of the imported parameters (zod schema)', async () => {
958
+ const schema = z.object({
959
+ path: z
960
+ .string()
961
+ .describe(
962
+ 'A list of paths to add to the PATH environment variable'
963
+ ),
964
+ paths: z
965
+ .array(z.string())
966
+ .describe(
967
+ 'A list of paths to add to the PATH environment variable'
968
+ ),
969
+ prepend: z
970
+ .boolean()
971
+ .describe(
972
+ 'Whether to prepend the paths to the PATH environment variable'
973
+ ),
974
+ declarationsOnly: z
975
+ .boolean()
976
+ .describe(
977
+ 'Whether to only declare the paths in the PATH environment variable'
978
+ ),
979
+ })
980
+
981
+ const resource = new class extends TestResource {
982
+ getSettings(): ResourceSettings<any> {
983
+ return {
984
+ id: 'path',
985
+ schema,
986
+ operatingSystems: [OS.Darwin],
987
+ parameterSettings: {
988
+ path: { type: 'directory' },
989
+ paths: { canModify: true, type: 'array', itemType: 'directory' },
990
+ prepend: { default: false, setting: true },
991
+ declarationsOnly: { default: false, setting: true },
992
+ },
993
+ importAndDestroy: {
994
+ refreshMapper: (input, context) => {
995
+ if (Object.keys(input).length === 0) {
996
+ return { paths: [], declarationsOnly: true };
997
+ }
998
+
999
+ return input;
1000
+ }
1001
+ },
1002
+ allowMultiple: {
1003
+ matcher: (desired, current) => {
1004
+ if (desired.path) {
1005
+ return desired.path === current.path;
1006
+ }
1007
+
1008
+ const currentPaths = new Set(current.paths)
1009
+ return desired.paths?.some((p) => currentPaths.has(p));
1010
+ }
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
1016
+ return {
1017
+ paths: [
1018
+ `${os.homedir()}/.pyenv/bin`,
1019
+ `${os.homedir()}/.bun/bin`,
1020
+ `${os.homedir()}/.deno/bin`,
1021
+ `${os.homedir()}/.jenv/bin`,
1022
+ `${os.homedir()}/a/random/path`,
1023
+ `${os.homedir()}/.nvm/.bin/2`,
1024
+ `${os.homedir()}/.nvm/.bin/3`
1025
+ ]
1026
+ }
1027
+ }
1028
+ }
1029
+
1030
+ const oldProcessEnv = structuredClone(process.env);
1031
+
1032
+ process.env['PYENV_ROOT'] = `${os.homedir()}/.pyenv`
1033
+ process.env['BUN_INSTALL'] = `${os.homedir()}/.bun`
1034
+ process.env['DENO_INSTALL'] = `${os.homedir()}/.deno`
1035
+ process.env['JENV'] = `${os.homedir()}/.jenv`
1036
+ process.env['NVM_DIR'] = `${os.homedir()}/.nvm`
1037
+
1038
+ const controller = new ResourceController(resource);
1039
+ const importResult1 = await controller.import({ type: 'path' }, {});
1040
+ expect(importResult1).toMatchObject([
1041
+ {
1042
+ 'core': {
1043
+ 'type': 'path'
1044
+ },
1045
+ 'parameters': {
1046
+ 'paths': [
1047
+ '$PYENV_ROOT/bin',
1048
+ '$BUN_INSTALL/bin',
1049
+ '$DENO_INSTALL/bin',
1050
+ '$JENV/bin',
1051
+ '~/a/random/path',
1052
+ '$NVM_DIR/.bin/2',
1053
+ '$NVM_DIR/.bin/3'
1054
+ ]
1055
+ }
1056
+ }
1057
+ ])
1058
+
1059
+ const importResult2 = await controller.import({ type: 'path' }, { paths: ['$PYENV_ROOT/bin', '$BUN_INSTALL/bin'] });
1060
+ expect(importResult2).toMatchObject([
1061
+ {
1062
+ 'core': {
1063
+ 'type': 'path'
1064
+ },
1065
+ 'parameters': {
1066
+ 'paths': [
1067
+ '$PYENV_ROOT/bin',
1068
+ '$BUN_INSTALL/bin',
1069
+ '$DENO_INSTALL/bin',
1070
+ '$JENV/bin',
1071
+ '~/a/random/path',
1072
+ '$NVM_DIR/.bin/2',
1073
+ '$NVM_DIR/.bin/3'
1074
+ ]
1075
+ }
1076
+ }
1077
+ ])
1078
+
1079
+ process.env = oldProcessEnv;
1080
+ })
955
1081
  });
@@ -36,18 +36,17 @@ export class ResourceController<T extends StringIndexedObject> {
36
36
 
37
37
  this.typeId = this.settings.id;
38
38
  this.dependencies = this.settings.dependencies ?? [];
39
+ this.parsedSettings = new ParsedResourceSettings<T>(this.settings);
39
40
 
40
- if (this.settings.schema) {
41
+ if (this.parsedSettings.schema) {
41
42
  this.ajv = new Ajv({
42
43
  allErrors: true,
43
44
  strict: true,
44
45
  strictRequired: false,
45
46
  allowUnionTypes: true
46
47
  })
47
- this.schemaValidator = this.ajv.compile(this.settings.schema);
48
+ this.schemaValidator = this.ajv.compile(this.parsedSettings.schema);
48
49
  }
49
-
50
- this.parsedSettings = new ParsedResourceSettings<T>(this.settings);
51
50
  }
52
51
 
53
52
  async initialize(): Promise<void> {
@@ -526,8 +525,8 @@ ${JSON.stringify(refresh, null, 2)}
526
525
  }
527
526
 
528
527
  private getAllParameterKeys(): string[] {
529
- return this.settings.schema
530
- ? Object.keys((this.settings.schema as any)?.properties)
528
+ return this.parsedSettings.schema
529
+ ? Object.keys((this.parsedSettings.schema as any)?.properties)
531
530
  : Object.keys(this.parsedSettings.parameterSettings);
532
531
  }
533
532
 
@@ -13,6 +13,7 @@ import { ArrayParameterSetting, ParameterSetting, ResourceSettings } from './res
13
13
  import { ResourceController } from './resource-controller.js';
14
14
  import os from 'node:os';
15
15
  import path from 'node:path';
16
+ import { z } from 'zod';
16
17
 
17
18
  describe('Resource parameter tests', () => {
18
19
  it('Generates a resource plan that includes stateful parameters (create)', async () => {
@@ -1174,4 +1175,39 @@ describe('Resource parameter tests', () => {
1174
1175
  expect(from2).to.eq('$HOME/abc/def')
1175
1176
 
1176
1177
  })
1178
+
1179
+ it('Can match directories 2', async () => {
1180
+
1181
+ const schema = z.object({
1182
+ propA: z.string(),
1183
+ propB: z.number(),
1184
+ });
1185
+
1186
+ const resource = new class extends TestResource {
1187
+ getSettings(): ResourceSettings<z.infer<typeof schema>> {
1188
+ return {
1189
+ id: 'resourceType',
1190
+ schema,
1191
+ operatingSystems: [OS.Darwin],
1192
+ parameterSettings: {
1193
+ propA: { type: 'directory' }
1194
+ },
1195
+ }
1196
+ }
1197
+ };
1198
+
1199
+ const controller = new ResourceController(resource);
1200
+ const transformations = controller.parsedSettings.inputTransformations.propA;
1201
+
1202
+ const to = transformations!.to('$HOME/abc/def')
1203
+ expect(to).to.eq(os.homedir() + '/abc/def')
1204
+
1205
+ const from = transformations!.from(os.homedir() + '/abc/def')
1206
+ expect(from).to.eq('~/abc/def')
1207
+
1208
+ const from2 = transformations!.from(os.homedir() + '/abc/def', '$HOME/abc/def')
1209
+ expect(from2).to.eq('$HOME/abc/def')
1210
+
1211
+ })
1212
+
1177
1213
  })
@@ -1,7 +1,8 @@
1
1
  import { JSONSchemaType } from 'ajv';
2
- import { OS, StringIndexedObject } from 'codify-schemas';
2
+ import { LinuxDistro, OS, StringIndexedObject } from 'codify-schemas';
3
3
  import isObjectsEqual from 'lodash.isequal'
4
4
  import path from 'node:path';
5
+ import { ZodObject } from 'zod';
5
6
 
6
7
  import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
7
8
  import {
@@ -11,6 +12,7 @@ import {
11
12
  tildify,
12
13
  untildify
13
14
  } from '../utils/functions.js';
15
+ import { ParsedResourceSettings } from './parsed-resource-settings.js';
14
16
  import { RefreshContext } from './resource.js';
15
17
 
16
18
  export interface InputTransformation {
@@ -33,10 +35,15 @@ export interface ResourceSettings<T extends StringIndexedObject> {
33
35
  */
34
36
  operatingSystems: Array<OS>;
35
37
 
38
+ /**
39
+ * List of supported linux distros
40
+ */
41
+ linuxDistros?: Array<LinuxDistro>;
42
+
36
43
  /**
37
44
  * Schema to validate user configs with. Must be in the format JSON Schema draft07
38
45
  */
39
- schema?: Partial<JSONSchemaType<T | any>>;
46
+ schema?: Partial<JSONSchemaType<T | any>> | ZodObject;
40
47
 
41
48
  /**
42
49
  * Mark the resource as sensitive. Defaults to false. This prevents the resource from automatically being imported by init and import.
@@ -44,6 +51,11 @@ export interface ResourceSettings<T extends StringIndexedObject> {
44
51
  */
45
52
  isSensitive?: boolean;
46
53
 
54
+ /**
55
+ * An optional description of the resource. This does not affect the behavior of the resource.
56
+ */
57
+ description?: string;
58
+
47
59
  /**
48
60
  * Allow multiple of the same resource to unique. Set truthy if
49
61
  * multiples are allowed, for example for applications, there can be multiple copy of the same application installed
@@ -349,7 +361,7 @@ export interface StatefulParameterSetting extends DefaultParameterSetting {
349
361
 
350
362
  const ParameterEqualsDefaults: Partial<Record<ParameterSettingType, (a: unknown, b: unknown) => boolean>> = {
351
363
  'boolean': (a: unknown, b: unknown) => Boolean(a) === Boolean(b),
352
- 'directory': (a: unknown, b: unknown) => {
364
+ 'directory'(a: unknown, b: unknown) {
353
365
  let transformedA = resolvePathWithVariables(untildify(String(a)))
354
366
  let transformedB = resolvePathWithVariables(untildify(String(b)))
355
367
 
@@ -430,7 +442,7 @@ export function resolveFnFromEqualsFnOrString(
430
442
  const ParameterTransformationDefaults: Partial<Record<ParameterSettingType, InputTransformation>> = {
431
443
  'directory': {
432
444
  to: (a: unknown) => resolvePathWithVariables((untildify(String(a)))),
433
- from: (a: unknown, original) => {
445
+ from(a: unknown, original) {
434
446
  if (ParameterEqualsDefaults.directory!(a, original)) {
435
447
  return original;
436
448
  }
@@ -489,7 +501,7 @@ export function resolveParameterTransformFn(
489
501
  }
490
502
 
491
503
  export function resolveMatcher<T extends StringIndexedObject>(
492
- settings: ResourceSettings<T>
504
+ settings: ParsedResourceSettings<T>
493
505
  ): (desired: Partial<T>, current: Partial<T>) => boolean {
494
506
  return typeof settings.allowMultiple === 'boolean' || !settings.allowMultiple?.matcher
495
507
  ? ((desired: Partial<T>, current: Partial<T>) => {
@@ -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
+ })