codify-plugin-lib 1.0.181 → 1.0.182-beta10

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 (46) hide show
  1. package/dist/index.d.ts +4 -1
  2. package/dist/index.js +4 -1
  3. package/dist/plugin/plugin.js +5 -2
  4. package/dist/pty/background-pty.d.ts +1 -0
  5. package/dist/pty/background-pty.js +17 -6
  6. package/dist/pty/index.d.ts +19 -1
  7. package/dist/pty/seqeuntial-pty.d.ts +17 -0
  8. package/dist/pty/seqeuntial-pty.js +117 -0
  9. package/dist/resource/parsed-resource-settings.d.ts +3 -1
  10. package/dist/resource/parsed-resource-settings.js +4 -6
  11. package/dist/resource/resource-settings.d.ts +5 -1
  12. package/dist/resource/resource-settings.js +1 -1
  13. package/dist/test.d.ts +1 -0
  14. package/dist/test.js +5 -0
  15. package/dist/utils/file-utils.d.ts +16 -0
  16. package/dist/utils/file-utils.js +172 -0
  17. package/dist/utils/functions.d.ts +12 -0
  18. package/dist/utils/functions.js +74 -0
  19. package/dist/utils/index.d.ts +26 -0
  20. package/dist/utils/index.js +111 -0
  21. package/dist/utils/internal-utils.d.ts +12 -0
  22. package/dist/utils/internal-utils.js +74 -0
  23. package/dist/utils/verbosity-level.d.ts +5 -0
  24. package/dist/utils/verbosity-level.js +9 -0
  25. package/package.json +2 -2
  26. package/src/index.ts +4 -1
  27. package/src/plan/plan.test.ts +6 -1
  28. package/src/plugin/plugin.test.ts +11 -2
  29. package/src/plugin/plugin.ts +5 -2
  30. package/src/pty/background-pty.ts +18 -6
  31. package/src/pty/index.test.ts +7 -4
  32. package/src/pty/index.ts +20 -2
  33. package/src/pty/seqeuntial-pty.ts +148 -0
  34. package/src/pty/sequential-pty.test.ts +179 -0
  35. package/src/resource/parsed-resource-settings.ts +8 -7
  36. package/src/resource/resource-controller-stateful-mode.test.ts +2 -1
  37. package/src/resource/resource-controller.test.ts +22 -4
  38. package/src/resource/resource-settings.test.ts +29 -2
  39. package/src/resource/resource-settings.ts +13 -2
  40. package/src/utils/file-utils.test.ts +7 -0
  41. package/src/utils/file-utils.ts +216 -0
  42. package/src/utils/{utils.ts → functions.ts} +0 -16
  43. package/src/utils/index.ts +144 -0
  44. package/src/utils/{utils.test.ts → internal-utils.test.ts} +1 -1
  45. package/src/utils/test-utils.test.ts +5 -2
  46. package/src/utils/verbosity-level.ts +11 -0
@@ -0,0 +1,148 @@
1
+ import pty from '@homebridge/node-pty-prebuilt-multiarch';
2
+ import { Ajv } from 'ajv';
3
+ import { CommandRequestResponseData, CommandRequestResponseDataSchema, IpcMessageV2, MessageCmd } from 'codify-schemas';
4
+ import { nanoid } from 'nanoid';
5
+ import { EventEmitter } from 'node:events';
6
+ import stripAnsi from 'strip-ansi';
7
+
8
+ import { Shell, Utils } from '../utils/index.js';
9
+ import { VerbosityLevel } from '../utils/verbosity-level.js';
10
+ import { IPty, SpawnError, SpawnOptions, SpawnResult, SpawnStatus } from './index.js';
11
+
12
+ EventEmitter.defaultMaxListeners = 1000;
13
+
14
+ const ajv = new Ajv({
15
+ strict: true,
16
+ });
17
+ const validateSudoRequestResponse = ajv.compile(CommandRequestResponseDataSchema);
18
+
19
+ /**
20
+ * The background pty is a specialized pty designed for speed. It can launch multiple tasks
21
+ * in parallel by moving them to the background. It attaches unix FIFO pipes to each process
22
+ * to listen to stdout and stderr. One limitation of the BackgroundPty is that the tasks run
23
+ * without a tty (or even a stdin) attached so interactive commands will not work.
24
+ */
25
+ export class SequentialPty implements IPty {
26
+ async spawn(cmd: string, options?: SpawnOptions): Promise<SpawnResult> {
27
+ const spawnResult = await this.spawnSafe(cmd, options);
28
+
29
+ if (spawnResult.status !== 'success') {
30
+ throw new SpawnError(cmd, spawnResult.exitCode, spawnResult.data);
31
+ }
32
+
33
+ return spawnResult;
34
+ }
35
+
36
+ async spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult> {
37
+ if (cmd.includes('sudo')) {
38
+ throw new Error('Do not directly use sudo. Use the option { requiresRoot: true } instead')
39
+ }
40
+
41
+ // If sudo is required, we must delegate to the main codify process.
42
+ if (options?.stdin || options?.requiresRoot) {
43
+ return this.externalSpawn(cmd, options);
44
+ }
45
+
46
+ console.log(`Running command: ${cmd}` + (options?.cwd ? `(${options?.cwd})` : ''))
47
+
48
+ return new Promise((resolve) => {
49
+ const output: string[] = [];
50
+ const historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
51
+
52
+ // If TERM_PROGRAM=Apple_Terminal is set then ANSI escape characters may be included
53
+ // in the response.
54
+ const env = {
55
+ ...process.env, ...options?.env,
56
+ TERM_PROGRAM: 'codify',
57
+ COMMAND_MODE: 'unix2003',
58
+ COLORTERM: 'truecolor',
59
+ ...historyIgnore
60
+ }
61
+
62
+ // Initial terminal dimensions
63
+ const initialCols = process.stdout.columns ?? 80;
64
+ const initialRows = process.stdout.rows ?? 24;
65
+
66
+ const args = options?.interactive ? ['-i', '-c', cmd] : ['-c', cmd]
67
+
68
+ // Run the command in a pty for interactivity
69
+ const mPty = pty.spawn(this.getDefaultShell(), args, {
70
+ ...options,
71
+ cols: initialCols,
72
+ rows: initialRows,
73
+ env
74
+ });
75
+
76
+ mPty.onData((data) => {
77
+ if (VerbosityLevel.get() > 0) {
78
+ process.stdout.write(data);
79
+ }
80
+
81
+ output.push(data.toString());
82
+ })
83
+
84
+ const resizeListener = () => {
85
+ const { columns, rows } = process.stdout;
86
+ mPty.resize(columns, rows);
87
+ }
88
+
89
+ // Listen to resize events for the terminal window;
90
+ process.stdout.on('resize', resizeListener);
91
+
92
+ mPty.onExit((result) => {
93
+ process.stdout.off('resize', resizeListener);
94
+
95
+ resolve({
96
+ status: result.exitCode === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
97
+ exitCode: result.exitCode,
98
+ data: stripAnsi(output.join('\n').trim()),
99
+ })
100
+ })
101
+ })
102
+ }
103
+
104
+ async kill(): Promise<{ exitCode: number, signal?: number | undefined }> {
105
+ // No-op here. Each pty instance is stand alone and tied to the parent process. Everything should be killed as expected.
106
+ return {
107
+ exitCode: 0,
108
+ signal: 0,
109
+ }
110
+ }
111
+
112
+ // For safety reasons, requests that require sudo or are interactive must be run via the main client
113
+ async externalSpawn(
114
+ cmd: string,
115
+ opts: SpawnOptions
116
+ ): Promise<SpawnResult> {
117
+ return new Promise((resolve) => {
118
+ const requestId = nanoid(8);
119
+
120
+ const listener = (data: IpcMessageV2) => {
121
+ if (data.requestId === requestId) {
122
+ process.removeListener('message', listener);
123
+
124
+ if (!validateSudoRequestResponse(data.data)) {
125
+ throw new Error(`Invalid response for sudo request: ${JSON.stringify(validateSudoRequestResponse.errors, null, 2)}`);
126
+ }
127
+
128
+ resolve(data.data as unknown as CommandRequestResponseData);
129
+ }
130
+ }
131
+
132
+ process.on('message', listener);
133
+
134
+ process.send!(<IpcMessageV2>{
135
+ cmd: MessageCmd.COMMAND_REQUEST,
136
+ data: {
137
+ command: cmd,
138
+ options: opts ?? {},
139
+ },
140
+ requestId
141
+ })
142
+ });
143
+ }
144
+
145
+ private getDefaultShell(): string {
146
+ return process.env.SHELL!;
147
+ }
148
+ }
@@ -0,0 +1,179 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { SequentialPty } from './seqeuntial-pty.js';
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';
6
+
7
+ describe('SequentialPty tests', () => {
8
+ it('Can launch a simple command', async () => {
9
+ const pty = new SequentialPty();
10
+
11
+ VerbosityLevel.set(1);
12
+
13
+ const result = await pty.spawnSafe('ls');
14
+ expect(result).toMatchObject({
15
+ status: 'success',
16
+ exitCode: 0,
17
+ data: expect.any(String),
18
+ })
19
+
20
+ const exitCode = await pty.kill();
21
+ expect(exitCode).toMatchObject({
22
+ exitCode: 0,
23
+ });
24
+ })
25
+
26
+ it('Reports back the correct exit code and status', async () => {
27
+ const pty = new SequentialPty();
28
+
29
+ const resultSuccess = await pty.spawnSafe('ls');
30
+ expect(resultSuccess).toMatchObject({
31
+ status: 'success',
32
+ exitCode: 0,
33
+ })
34
+
35
+ const resultFailed = await pty.spawnSafe('which sjkdhsakjdhjkash');
36
+ expect(resultFailed).toMatchObject({
37
+ status: 'error',
38
+ exitCode: 1,
39
+ data: 'sjkdhsakjdhjkash not found' // This might change on different os or shells. Keep for now.
40
+ })
41
+ });
42
+
43
+ it('Can use a different cwd', async () => {
44
+ const pty = new SequentialPty();
45
+
46
+ const resultSuccess = await pty.spawnSafe('pwd', { cwd: '/tmp' });
47
+ expect(resultSuccess).toMatchObject({
48
+ status: 'success',
49
+ exitCode: 0,
50
+ data: '/tmp'
51
+ })
52
+ });
53
+
54
+ it('It can launch a command in interactive mode', async () => {
55
+ const originalSend = process.send;
56
+ process.send = (req: IpcMessageV2) => {
57
+ expect(req).toMatchObject({
58
+ cmd: MessageCmd.COMMAND_REQUEST,
59
+ requestId: expect.any(String),
60
+ data: {
61
+ command: 'ls',
62
+ options: {
63
+ cwd: '/tmp',
64
+ interactive: true,
65
+ }
66
+ }
67
+ })
68
+
69
+ // This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
70
+ const listeners = process.listeners('message');
71
+ listeners[2](({
72
+ cmd: MessageCmd.COMMAND_REQUEST,
73
+ requestId: req.requestId,
74
+ status: MessageStatus.SUCCESS,
75
+ data: {
76
+ status: SpawnStatus.SUCCESS,
77
+ exitCode: 0,
78
+ data: 'My data',
79
+ }
80
+ }))
81
+
82
+ return true;
83
+ }
84
+
85
+ const $ = new SequentialPty();
86
+ const resultSuccess = await $.spawnSafe('ls', { interactive: true, cwd: '/tmp' });
87
+
88
+ expect(resultSuccess).toMatchObject({
89
+ status: 'success',
90
+ exitCode: 0,
91
+ });
92
+
93
+ process.send = originalSend;
94
+ });
95
+
96
+ it('It can work with root (sudo)', async () => {
97
+ const originalSend = process.send;
98
+ process.send = (req: IpcMessageV2) => {
99
+ expect(req).toMatchObject({
100
+ cmd: MessageCmd.COMMAND_REQUEST,
101
+ requestId: expect.any(String),
102
+ data: {
103
+ command: 'ls',
104
+ options: {
105
+ interactive: true,
106
+ requiresRoot: true,
107
+ }
108
+ }
109
+ })
110
+
111
+ // This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
112
+ const listeners = process.listeners('message');
113
+ listeners[2](({
114
+ cmd: MessageCmd.COMMAND_REQUEST,
115
+ requestId: req.requestId,
116
+ status: MessageStatus.SUCCESS,
117
+ data: {
118
+ status: SpawnStatus.SUCCESS,
119
+ exitCode: 0,
120
+ data: 'My data',
121
+ }
122
+ }))
123
+
124
+ return true;
125
+ }
126
+
127
+ const $ = new SequentialPty();
128
+ const resultSuccess = await $.spawn('ls', { interactive: true, requiresRoot: true });
129
+
130
+ expect(resultSuccess).toMatchObject({
131
+ status: 'success',
132
+ exitCode: 0,
133
+ });
134
+
135
+ process.send = originalSend;
136
+ })
137
+
138
+ it('It can handle errors when in sudo', async () => {
139
+ const originalSend = process.send;
140
+ process.send = (req: IpcMessageV2) => {
141
+ expect(req).toMatchObject({
142
+ cmd: MessageCmd.COMMAND_REQUEST,
143
+ requestId: expect.any(String),
144
+ data: {
145
+ command: 'ls',
146
+ options: {
147
+ requiresRoot: true,
148
+ interactive: true,
149
+ }
150
+ }
151
+ })
152
+
153
+ // This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
154
+ const listeners = process.listeners('message');
155
+ listeners[2](({
156
+ cmd: MessageCmd.COMMAND_REQUEST,
157
+ requestId: req.requestId,
158
+ status: MessageStatus.SUCCESS,
159
+ data: {
160
+ status: SpawnStatus.ERROR,
161
+ exitCode: 127,
162
+ data: 'My data',
163
+ }
164
+ }))
165
+
166
+ return true;
167
+ }
168
+
169
+ const $ = new SequentialPty();
170
+ const resultSuccess = await $.spawnSafe('ls', { interactive: true, requiresRoot: true });
171
+
172
+ expect(resultSuccess).toMatchObject({
173
+ status: SpawnStatus.ERROR,
174
+ exitCode: 127,
175
+ });
176
+
177
+ process.send = originalSend;
178
+ })
179
+ })
@@ -1,5 +1,5 @@
1
1
  import { JSONSchemaType } from 'ajv';
2
- import { StringIndexedObject } from 'codify-schemas';
2
+ import { OS, StringIndexedObject } from 'codify-schemas';
3
3
 
4
4
  import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
5
5
  import {
@@ -46,16 +46,17 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
46
46
  removeStatefulParametersBeforeDestroy?: boolean | undefined;
47
47
  dependencies?: string[] | undefined;
48
48
  transformation?: InputTransformation;
49
+
50
+ operatingSystems!: Array<OS>;
51
+ isSensitive?: boolean;
52
+
49
53
  private settings: ResourceSettings<T>;
50
54
 
51
55
  constructor(settings: ResourceSettings<T>) {
52
56
  this.settings = settings;
53
- this.id = settings.id;
54
- this.schema = settings.schema;
55
- this.allowMultiple = settings.allowMultiple;
56
- this.removeStatefulParametersBeforeDestroy = settings.removeStatefulParametersBeforeDestroy;
57
- this.dependencies = settings.dependencies;
58
- this.transformation = settings.transformation;
57
+
58
+ const { parameterSettings, ...rest } = settings;
59
+ Object.assign(this, rest);
59
60
 
60
61
  this.validateSettings();
61
62
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { ParameterOperation, ResourceOperation } from 'codify-schemas';
2
+ import { OS, ParameterOperation, ResourceOperation } from 'codify-schemas';
3
3
  import { TestConfig, TestResource, TestStatefulParameter } from '../utils/test-utils.test.js';
4
4
  import { ResourceSettings } from './resource-settings.js';
5
5
  import { ResourceController } from './resource-controller.js';
@@ -179,6 +179,7 @@ describe('Resource tests for stateful plans', () => {
179
179
  getSettings(): ResourceSettings<TestConfig> {
180
180
  return {
181
181
  id: 'type',
182
+ operatingSystems: [OS.Darwin],
182
183
  parameterSettings: {
183
184
  propD: { type: 'stateful', definition: statefulParameter },
184
185
  }
@@ -1,5 +1,5 @@
1
1
  import { Resource } from './resource.js';
2
- import { ResourceOperation } from 'codify-schemas';
2
+ import { OS, ResourceOperation } from 'codify-schemas';
3
3
  import { spy } from 'sinon';
4
4
  import { describe, expect, it } from 'vitest'
5
5
  import { ArrayParameterSetting, ParameterSetting, ResourceSettings } from './resource-settings.js';
@@ -7,7 +7,7 @@ import { CreatePlan, DestroyPlan, ModifyPlan } from '../plan/plan-types.js';
7
7
  import { ParameterChange } from '../plan/change-set.js';
8
8
  import { ResourceController } from './resource-controller.js';
9
9
  import { TestConfig, testPlan, TestResource, TestStatefulParameter } from '../utils/test-utils.test.js';
10
- import { tildify, untildify } from '../utils/utils.js';
10
+ 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';
@@ -19,6 +19,7 @@ describe('Resource tests', () => {
19
19
  getSettings(): ResourceSettings<TestConfig> {
20
20
  return {
21
21
  id: 'type',
22
+ operatingSystems: [OS.Darwin],
22
23
  dependencies: ['homebrew', 'python'],
23
24
  parameterSettings: {
24
25
  propA: {
@@ -197,6 +198,7 @@ describe('Resource tests', () => {
197
198
  getSettings(): ResourceSettings<TestConfig> {
198
199
  return {
199
200
  id: 'resource',
201
+ operatingSystems: [OS.Darwin],
200
202
  parameterSettings: {
201
203
  propA: { canModify: true },
202
204
  propB: { canModify: true },
@@ -232,6 +234,7 @@ describe('Resource tests', () => {
232
234
  getSettings(): ResourceSettings<TestConfig> {
233
235
  return {
234
236
  id: 'type',
237
+ operatingSystems: [OS.Darwin],
235
238
  dependencies: ['homebrew', 'python'],
236
239
  parameterSettings: {
237
240
  propA: { canModify: true },
@@ -254,6 +257,7 @@ describe('Resource tests', () => {
254
257
  getSettings(): ResourceSettings<TestConfig> {
255
258
  return {
256
259
  id: 'type',
260
+ operatingSystems: [OS.Darwin],
257
261
  dependencies: ['homebrew', 'python'],
258
262
  parameterSettings: {
259
263
  propA: { canModify: true },
@@ -270,6 +274,7 @@ describe('Resource tests', () => {
270
274
  getSettings(): ResourceSettings<TestConfig> {
271
275
  return {
272
276
  id: 'type',
277
+ operatingSystems: [OS.Darwin],
273
278
  parameterSettings: {
274
279
  propA: { default: 'propADefault' }
275
280
  }
@@ -298,6 +303,7 @@ describe('Resource tests', () => {
298
303
  getSettings(): ResourceSettings<TestConfig> {
299
304
  return {
300
305
  id: 'type',
306
+ operatingSystems: [OS.Darwin],
301
307
  parameterSettings: {
302
308
  propE: { default: 'propEDefault' }
303
309
  }
@@ -325,6 +331,7 @@ describe('Resource tests', () => {
325
331
  getSettings(): ResourceSettings<TestConfig> {
326
332
  return {
327
333
  id: 'type',
334
+ operatingSystems: [OS.Darwin],
328
335
  parameterSettings: {
329
336
  propE: { default: 'propEDefault' }
330
337
  }
@@ -348,6 +355,7 @@ describe('Resource tests', () => {
348
355
  getSettings(): ResourceSettings<TestConfig> {
349
356
  return {
350
357
  id: 'type',
358
+ operatingSystems: [OS.Darwin],
351
359
  parameterSettings: {
352
360
  propA: { default: 'propADefault' }
353
361
  }
@@ -376,6 +384,7 @@ describe('Resource tests', () => {
376
384
  getSettings(): ResourceSettings<TestConfig> {
377
385
  return {
378
386
  id: 'type',
387
+ operatingSystems: [OS.Darwin],
379
388
  parameterSettings: {
380
389
  propA: { default: 'propADefault' }
381
390
  }
@@ -392,7 +401,7 @@ describe('Resource tests', () => {
392
401
  it('Has the correct typing for applys', () => {
393
402
  const resource = new class extends Resource<TestConfig> {
394
403
  getSettings(): ResourceSettings<TestConfig> {
395
- return { id: 'type' }
404
+ return { id: 'type', operatingSystems: [OS.Darwin], }
396
405
  }
397
406
 
398
407
  async refresh(): Promise<Partial<TestConfig> | null> {
@@ -418,7 +427,8 @@ describe('Resource tests', () => {
418
427
  const parameter1 = new class extends StatefulParameter<any, any> {
419
428
  getSettings(): ParameterSetting {
420
429
  return {
421
- type: 'version'
430
+ type: 'version',
431
+ operatingSystems: [OS.Darwin],
422
432
  }
423
433
  }
424
434
 
@@ -462,6 +472,7 @@ describe('Resource tests', () => {
462
472
  getSettings(): ResourceSettings<TestConfig> {
463
473
  return {
464
474
  id: 'nvm',
475
+ operatingSystems: [OS.Darwin],
465
476
  parameterSettings: {
466
477
  global: { type: 'stateful', definition: parameter1, order: 2 },
467
478
  nodeVersions: { type: 'stateful', definition: parameter2, order: 1 },
@@ -514,6 +525,7 @@ describe('Resource tests', () => {
514
525
  getSettings(): ResourceSettings<TestConfig> {
515
526
  return {
516
527
  id: 'nvm',
528
+ operatingSystems: [OS.Darwin],
517
529
  parameterSettings: {
518
530
  global: { type: 'stateful', definition: parameter1, order: 2 },
519
531
  nodeVersions: { type: 'stateful', definition: parameter2, order: 1 },
@@ -546,6 +558,7 @@ describe('Resource tests', () => {
546
558
  getSettings(): ResourceSettings<TestConfig> {
547
559
  return {
548
560
  id: 'resourceType',
561
+ operatingSystems: [OS.Darwin],
549
562
  parameterSettings: {
550
563
  propD: {
551
564
  type: 'array',
@@ -630,6 +643,7 @@ describe('Resource tests', () => {
630
643
  getSettings(): ResourceSettings<TestConfig> {
631
644
  return {
632
645
  id: 'resourceType',
646
+ operatingSystems: [OS.Darwin],
633
647
  parameterSettings: {
634
648
  propD: {
635
649
  type: 'array',
@@ -720,6 +734,7 @@ describe('Resource tests', () => {
720
734
  getSettings(): ResourceSettings<TestConfig> {
721
735
  return {
722
736
  id: 'resourceType',
737
+ operatingSystems: [OS.Darwin],
723
738
  parameterSettings: {
724
739
  propA: { type: 'string', default: 'defaultValue' },
725
740
  propB: { type: 'boolean', default: true }
@@ -755,6 +770,7 @@ describe('Resource tests', () => {
755
770
  getSettings(): ResourceSettings<any> {
756
771
  return {
757
772
  id: 'path',
773
+ operatingSystems: [OS.Darwin],
758
774
  parameterSettings: {
759
775
  path: { type: 'string', isEqual: 'directory' },
760
776
  paths: { canModify: true, type: 'array', isElementEqual: 'directory' },
@@ -797,6 +813,7 @@ describe('Resource tests', () => {
797
813
  getSettings(): ResourceSettings<any> {
798
814
  return {
799
815
  id: 'path',
816
+ operatingSystems: [OS.Darwin],
800
817
  parameterSettings: {
801
818
  path: { type: 'string', isEqual: 'directory' },
802
819
  paths: { canModify: true, type: 'array', isElementEqual: 'directory' },
@@ -840,6 +857,7 @@ describe('Resource tests', () => {
840
857
  getSettings(): ResourceSettings<any> {
841
858
  return {
842
859
  id: 'path',
860
+ operatingSystems: [OS.Darwin],
843
861
  parameterSettings: {
844
862
  path: { type: 'directory' },
845
863
  paths: { canModify: true, type: 'array', itemType: 'directory' },