codify-plugin-test 0.0.53-beta2 → 0.0.53-beta20

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/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAC7B,MAA0B;IAE1B,MAAM,cAAc,GAAG;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7D,CAAC;IAGF,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE,GAAG,MAAM,CAAC;IAExD,OAAO;QACL,UAAU,EAAE,UAAe;QAC3B,cAAc;KACf,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,UAAU,EAAuB,MAAM,gBAAgB,CAAC;AACjF,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,UAAU,eAAe,CAC7B,MAA0B;IAE1B,MAAM,cAAc,GAAG;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC;IAEF,6DAA6D;IAC7D,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,UAAU,EAAE,GAAG,MAAM,CAAC;IAE5D,OAAO;QACL,UAAU,EAAE,UAAe;QAC3B,cAAc;KACf,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAC7B,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO,UAAU,CAAC,OAAO,CAAC;QAC5B,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codify-plugin-test",
3
- "version": "0.0.53-beta2",
3
+ "version": "0.0.53-beta20",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "ajv": "^8.12.0",
18
18
  "ajv-formats": "^3.0.1",
19
- "codify-schemas": "1.0.86-beta5",
19
+ "codify-schemas": "1.0.86-beta7",
20
20
  "lodash.matches": "^4.6.0",
21
21
  "lodash.differencewith": "4.5.0",
22
22
  "lodash.unionby": "^4.8.0",
@@ -37,7 +37,7 @@
37
37
  "eslint-config-oclif-typescript": "^3",
38
38
  "eslint-config-prettier": "^9.0.0",
39
39
  "tsx": "^4.7.3",
40
- "typescript": "^5",
40
+ "typescript": "5.3.3",
41
41
  "vitest": "^1.4.0",
42
42
  "codify-plugin-lib": "1.0.182-beta9"
43
43
  },
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './plugin-tester.js'
2
+ export * from './spawn.js'
2
3
  export * from './test-utils.js'
@@ -21,7 +21,7 @@ import * as os from 'node:os';
21
21
  import path from 'node:path';
22
22
 
23
23
  import { spawnSafe } from './spawn.js';
24
- import { CodifyTestUtils } from './test-utils.js';
24
+ import { TestUtils } from './test-utils.js';
25
25
 
26
26
  const ajv = new Ajv.default({
27
27
  strict: true
@@ -64,7 +64,7 @@ export class PluginProcess {
64
64
  }
65
65
 
66
66
  async initialize(): Promise<InitializeResponseData> {
67
- return CodifyTestUtils.sendMessageAndAwaitResponse(this.childProcess, {
67
+ return TestUtils.sendMessageAndAwaitResponse(this.childProcess, {
68
68
  cmd: 'initialize',
69
69
  data: { verbosityLevel: 3 },
70
70
  requestId: nanoid(6),
@@ -72,7 +72,7 @@ export class PluginProcess {
72
72
  }
73
73
 
74
74
  async validate(data: ValidateRequestData): Promise<ValidateResponseData> {
75
- return CodifyTestUtils.sendMessageAndAwaitResponse(this.childProcess, {
75
+ return TestUtils.sendMessageAndAwaitResponse(this.childProcess, {
76
76
  cmd: 'validate',
77
77
  data,
78
78
  requestId: nanoid(6),
@@ -80,7 +80,7 @@ export class PluginProcess {
80
80
  }
81
81
 
82
82
  async plan(data: PlanRequestData): Promise<PlanResponseData> {
83
- return CodifyTestUtils.sendMessageAndAwaitResponse(this.childProcess, {
83
+ return TestUtils.sendMessageAndAwaitResponse(this.childProcess, {
84
84
  cmd: 'plan',
85
85
  data,
86
86
  requestId: nanoid(6),
@@ -88,7 +88,7 @@ export class PluginProcess {
88
88
  }
89
89
 
90
90
  async apply(data: ApplyRequestData): Promise<void> {
91
- return CodifyTestUtils.sendMessageAndAwaitResponse(this.childProcess, {
91
+ return TestUtils.sendMessageAndAwaitResponse(this.childProcess, {
92
92
  cmd: 'apply',
93
93
  data,
94
94
  requestId: nanoid(6),
@@ -96,7 +96,7 @@ export class PluginProcess {
96
96
  }
97
97
 
98
98
  async import(data: ImportRequestData): Promise<ImportResponseData> {
99
- return CodifyTestUtils.sendMessageAndAwaitResponse(this.childProcess, {
99
+ return TestUtils.sendMessageAndAwaitResponse(this.childProcess, {
100
100
  cmd: 'import',
101
101
  data,
102
102
  requestId: nanoid(6),
@@ -107,9 +107,9 @@ export class PluginProcess {
107
107
  this.childProcess.kill();
108
108
  }
109
109
 
110
- private handleIncomingRequests(process: ChildProcess) {
110
+ private handleIncomingRequests(cp: ChildProcess) {
111
111
  // Listen for incoming sudo incoming sudo requests
112
- process.on('message', async (message) => {
112
+ cp.on('message', async (message) => {
113
113
  if (!ipcMessageValidator(message)) {
114
114
  throw new Error(`Invalid message from plugin. ${JSON.stringify(message, null, 2)}`);
115
115
  }
@@ -123,7 +123,7 @@ export class PluginProcess {
123
123
  const { command, options } = data as unknown as CommandRequestData;
124
124
  const result = await spawnSafe(command, options);
125
125
 
126
- process.send(<IpcMessageV2>{
126
+ cp.send(<IpcMessageV2>{
127
127
  cmd: MessageCmd.COMMAND_REQUEST + '_Response',
128
128
  data: result,
129
129
  requestId,
@@ -136,7 +136,7 @@ export class PluginProcess {
136
136
  throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(commandRequestValidator.errors, null, 2)}`);
137
137
  }
138
138
 
139
- process.send(<IpcMessageV2>{
139
+ cp.send(<IpcMessageV2>{
140
140
  cmd: MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST + '_Response',
141
141
  data: {},
142
142
  requestId,
@@ -146,19 +146,14 @@ export class PluginProcess {
146
146
  if (message.cmd === MessageCmd.CODIFY_CREDENTIALS_REQUEST) {
147
147
  const { requestId } = message;
148
148
 
149
- const loginJson = await fs.readFile(path.join(os.homedir(), '.codify', 'credentials.json'), 'utf8');
150
- if (!loginJson) {
151
- throw new Error('Unable to get login credentials')
149
+ const testJwt = process.env.VITE_CODIFY_TEST_JWT;
150
+ if (!testJwt) {
151
+ throw new Error('Unable to parse login credentials from VITE_CODIFY_TEST_JWT env var. Please set and try again');
152
152
  }
153
153
 
154
- const login = JSON.parse(loginJson);
155
- if (!login) {
156
- throw new Error('Unable to parse login credentials')
157
- }
158
-
159
- process.send(<IpcMessageV2>{
154
+ cp.send(<IpcMessageV2>{
160
155
  cmd: MessageCmd.CODIFY_CREDENTIALS_REQUEST + '_Response',
161
- data: login.accessToken,
156
+ data: testJwt,
162
157
  requestId,
163
158
  })
164
159
  }
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import {
3
- ImportResponseData,
3
+ ImportResponseData, OS,
4
4
  PlanResponseData,
5
5
  ResourceConfig,
6
6
  ResourceOperation,
@@ -8,7 +8,8 @@ import {
8
8
  import unionBy from 'lodash.unionby';
9
9
 
10
10
  import { PluginProcess } from './plugin-process.js';
11
- import { splitUserConfig } from './utils.js';
11
+ import { getPlatformOs, splitUserConfig } from './utils.js';
12
+ import os from 'node:os';
12
13
 
13
14
  export class PluginTester {
14
15
  static async fullTest(
@@ -25,9 +26,13 @@ export class PluginTester {
25
26
  modifiedConfigs: ResourceConfig[],
26
27
  validateModify?: (plans: PlanResponseData[]) => Promise<void> | void,
27
28
  }
28
- }): Promise<void> {
29
- const ids = configs.map((c) => c.name ? `${c.type}.${c.name}` : c.type).join(', ')
30
- console.info(chalk.cyan(`Starting full test of [ ${ids} ]...`))
29
+ }): Promise<void> {
30
+ configs = configs.filter((c) => !c.os || c.os.includes(getPlatformOs()));
31
+ const ids = configs
32
+ .map((c) => `${c.type}${c.name ? `.${c.name}` : ''}`)
33
+ .join(', ')
34
+ console.info(chalk.cyan(`Starting full test of [ ${ids} ]...`));
35
+
31
36
 
32
37
  const {
33
38
  skipUninstall = false,
@@ -46,11 +51,15 @@ export class PluginTester {
46
51
  throw new Error(`The plugin does not support the following configs supplied:\n ${JSON.stringify(unsupportedConfigs, null, 2)}\n Initialize result: ${JSON.stringify(initializeResult)}`)
47
52
  }
48
53
 
54
+ // configs = configs.filter((c) => initializeResult.resourceDefinitions.find((rd) => rd.type === c.type)?.operatingSystems?.includes(os.platform() as OS));
55
+
49
56
  console.info(chalk.cyan('Testing validate...'))
50
- const validate = await plugin.validate({ configs: configs.map((c) => {
51
- const { coreParameters, parameters } = splitUserConfig(c)
52
- return { core: coreParameters, parameters };
53
- }) });
57
+ const validate = await plugin.validate({
58
+ configs: configs.map((c) => {
59
+ const { coreParameters, parameters } = splitUserConfig(c)
60
+ return { core: coreParameters, parameters };
61
+ })
62
+ });
54
63
 
55
64
  const invalidConfigs = validate.resourceValidations.filter((v) => !v.isValid)
56
65
  if (invalidConfigs.length > 0) {
@@ -61,7 +70,7 @@ export class PluginTester {
61
70
  const plans = [];
62
71
  for (const config of configs) {
63
72
  const { coreParameters, parameters } = splitUserConfig(config);
64
-
73
+
65
74
  plans.push(await plugin.plan({
66
75
  core: coreParameters,
67
76
  desired: parameters,
@@ -91,7 +100,7 @@ export class PluginTester {
91
100
  if (!options?.skipImport) {
92
101
  const importPlugin = new PluginProcess(pluginPath);
93
102
  try {
94
- await plugin.initialize();
103
+ await importPlugin.initialize();
95
104
  console.info(chalk.cyan('Testing import...'))
96
105
 
97
106
  const importResults = [];
@@ -114,13 +123,13 @@ export class PluginTester {
114
123
  const modifyPlugin = new PluginProcess(pluginPath);
115
124
 
116
125
  try {
117
- await plugin.initialize();
126
+ await modifyPlugin.initialize();
118
127
  console.info(chalk.cyan('Testing modify...'))
119
128
 
120
129
  const modifyPlans = [];
121
130
  for (const config of options.testModify.modifiedConfigs) {
122
131
  const { coreParameters, parameters } = splitUserConfig(config);
123
-
132
+
124
133
  modifyPlans.push(await modifyPlugin.plan({
125
134
  core: coreParameters,
126
135
  desired: parameters,
@@ -160,6 +169,59 @@ ${JSON.stringify(modifyPlans, null, 2)}`)
160
169
  }
161
170
  }
162
171
 
172
+ static async install(pluginPath: string, configs: ResourceConfig[]) {
173
+ const plugin = new PluginProcess(pluginPath);
174
+
175
+ try {
176
+ console.info(chalk.cyan('Testing initialization...'))
177
+ const initializeResult = await plugin.initialize();
178
+
179
+ const unsupportedConfigs = configs.filter((c) =>
180
+ !initializeResult.resourceDefinitions.some((rd) => rd.type === c.type)
181
+ )
182
+ if (unsupportedConfigs.length > 0) {
183
+ throw new Error(`The plugin does not support the following configs supplied:\n ${JSON.stringify(unsupportedConfigs, null, 2)}\n Initialize result: ${JSON.stringify(initializeResult)}`)
184
+ }
185
+
186
+ // configs = configs.filter((c) => initializeResult.resourceDefinitions.find((rd) => rd.type === c.type)?.operatingSystems?.includes(os.platform() as OS));
187
+
188
+ console.info(chalk.cyan('Testing validate...'))
189
+ const validate = await plugin.validate({
190
+ configs: configs.map((c) => {
191
+ const { coreParameters, parameters } = splitUserConfig(c)
192
+ return { core: coreParameters, parameters };
193
+ })
194
+ });
195
+
196
+ const invalidConfigs = validate.resourceValidations.filter((v) => !v.isValid)
197
+ if (invalidConfigs.length > 0) {
198
+ throw new Error(`The following configs did not validate:\n ${JSON.stringify(invalidConfigs, null, 2)}`)
199
+ }
200
+
201
+ console.info(chalk.cyan('Testing plan...'))
202
+ const plans = [];
203
+ for (const config of configs) {
204
+ const { coreParameters, parameters } = splitUserConfig(config);
205
+
206
+ plans.push(await plugin.plan({
207
+ core: coreParameters,
208
+ desired: parameters,
209
+ isStateful: false,
210
+ state: undefined,
211
+ }));
212
+ }
213
+
214
+ console.info(chalk.cyan('Testing apply...'))
215
+ for (const plan of plans) {
216
+ await plugin.apply({
217
+ planId: plan.planId
218
+ });
219
+ }
220
+ } finally {
221
+ plugin.kill();
222
+ }
223
+ }
224
+
163
225
  static async uninstall(pluginPath: string, configs: ResourceConfig[], options?: {
164
226
  validateDestroy?: (plans: PlanResponseData[]) => Promise<void> | void
165
227
  }) {
@@ -172,7 +234,7 @@ ${JSON.stringify(modifyPlans, null, 2)}`)
172
234
  const plans = [];
173
235
  for (const config of configs) {
174
236
  const { coreParameters, parameters } = splitUserConfig(config);
175
-
237
+
176
238
  plans.push(await destroyPlugin.plan({
177
239
  core: coreParameters,
178
240
  isStateful: true,
@@ -206,7 +268,9 @@ ${JSON.stringify(modifyPlans, null, 2)}`)
206
268
  for (const type of typeSet) {
207
269
  const sameTypeConfigs = configs.filter((c) => c.type === type);
208
270
  if (sameTypeConfigs.length > 1) {
209
- sameTypeConfigs.forEach((c, idx) => { c.name = c.name ?? idx.toString() });
271
+ sameTypeConfigs.forEach((c, idx) => {
272
+ c.name = c.name ?? idx.toString()
273
+ });
210
274
  }
211
275
 
212
276
  configsWithNames.push(...sameTypeConfigs);
package/src/spawn.ts CHANGED
@@ -18,12 +18,16 @@ export interface SpawnOptions {
18
18
  stdin?: boolean,
19
19
  }
20
20
 
21
+ export function testSpawn(cmd: string, options?: SpawnOptions): Promise<SpawnResult> {
22
+ return spawnSafe(cmd, { interactive: true, ...options, });
23
+ }
24
+
21
25
  export function spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult> {
22
26
  if (cmd.toLowerCase().includes('sudo')) {
23
27
  throw new Error('Command must not include sudo')
24
28
  }
25
29
 
26
- process.stdout.write(`Running command: ${options?.requiresRoot ? 'sudo' : ''} ${cmd}` + (options?.cwd ? `(${options?.cwd})` : ''))
30
+ console.log(`Running command: ${options?.requiresRoot ? 'sudo' : ''} ${cmd}` + (options?.cwd ? `(${options?.cwd})` : ''))
27
31
 
28
32
  return new Promise((resolve) => {
29
33
  const output: string[] = [];
@@ -40,7 +44,7 @@ export function spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnRes
40
44
  }
41
45
 
42
46
  // Initial terminal dimensions
43
- const initialCols = process.stdout.columns ?? 80;
47
+ const initialCols = process.stdout.columns ?? 2000;
44
48
  const initialRows = process.stdout.rows ?? 24;
45
49
 
46
50
  const command = options?.requiresRoot ? `sudo ${cmd}` : cmd;
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { ChildProcess } from 'node:child_process';
3
3
  import { Readable } from 'stream';
4
- import { CodifyTestUtils } from './test-utils.js';
4
+ import { TestUtils } from './test-utils.js';
5
5
  import { describe, expect, it, vi } from 'vitest';
6
6
  import { MessageStatus } from 'codify-schemas';
7
7
  import { nanoid } from 'nanoid';
@@ -22,7 +22,7 @@ describe('Test Utils tests', async () => {
22
22
  const sendMock = vi.spyOn(process, 'send');
23
23
  const requestId = nanoid(6);
24
24
 
25
- CodifyTestUtils.sendMessageAndAwaitResponse(process, { cmd: 'message', data: 'data', requestId })
25
+ TestUtils.sendMessageAndAwaitResponse(process, { cmd: 'message', data: 'data', requestId })
26
26
 
27
27
  expect(sendMock.mock.calls.length).to.eq(1);
28
28
  expect(sendMock.mock.calls[0][0]).to.deep.eq({ cmd: 'message', data: 'data', requestId });
@@ -38,7 +38,7 @@ describe('Test Utils tests', async () => {
38
38
  // Note that the response must end in _Response. In accordance to the message schema rules.
39
39
  process.emit('message', { cmd: 'message_Response', status: MessageStatus.SUCCESS, data: 'data', requestId })
40
40
  })(),
41
- CodifyTestUtils.sendMessageAndAwaitResponse(process, { cmd: 'message', data: 'data', requestId }),
41
+ TestUtils.sendMessageAndAwaitResponse(process, { cmd: 'message', data: 'data', requestId }),
42
42
  ]);
43
43
 
44
44
  expect(result[1]).to.eq('data')
@@ -54,7 +54,7 @@ describe('Test Utils tests', async () => {
54
54
  // Note that the response must end in _Response. In accordance to the message schema rules.
55
55
  process.emit('message', { cmd: 'message_Response', status: MessageStatus.ERROR, data: 'error message', requestId })
56
56
  })(),
57
- CodifyTestUtils.sendMessageAndAwaitResponse(process, { cmd: 'message', data: 'data', requestId }),
57
+ TestUtils.sendMessageAndAwaitResponse(process, { cmd: 'message', data: 'data', requestId }),
58
58
  ])).rejects.toThrowError(new Error('error message'))
59
59
  });
60
60
 
@@ -70,7 +70,7 @@ describe('Test Utils tests', async () => {
70
70
  process.emit('message', { cmd: 'randomMessage2', status: MessageStatus.SUCCESS, data: 'message2', requestId: nanoid(6) })
71
71
  process.emit('message', { cmd: 'message_Response', status: MessageStatus.SUCCESS, data: 'data', requestId })
72
72
  })(),
73
- CodifyTestUtils.sendMessageAndAwaitResponse(process, { cmd: 'message', data: 'data', requestId }),
73
+ TestUtils.sendMessageAndAwaitResponse(process, { cmd: 'message', data: 'data', requestId }),
74
74
  ]);
75
75
 
76
76
  // Only the final _Response message should be returned.
package/src/test-utils.ts CHANGED
@@ -1,13 +1,18 @@
1
1
  import Ajv from 'ajv';
2
- import { IpcMessageSchema, IpcMessageV2, MessageStatus } from 'codify-schemas';
2
+ import { IpcMessageSchema, IpcMessageV2, MessageStatus, ResourceOs, SpawnStatus } from 'codify-schemas';
3
3
  import { ChildProcess } from 'node:child_process';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ import { PluginTester } from './plugin-tester.js';
8
+ import { testSpawn } from './spawn.js';
4
9
 
5
10
  const ajv = new Ajv.default({
6
11
  strict: true
7
12
  });
8
13
  const ipcMessageValidator = ajv.compile(IpcMessageSchema);
9
14
 
10
- export const CodifyTestUtils = {
15
+ export const TestUtils = {
11
16
  sendMessageAndAwaitResponse(process: ChildProcess, message: IpcMessageV2): Promise<any> {
12
17
  return new Promise((resolve, reject) => {
13
18
  process.on('message', (response: IpcMessageV2) => {
@@ -30,4 +35,113 @@ export const CodifyTestUtils = {
30
35
  });
31
36
  },
32
37
 
38
+ async ensureHomebrewInstalledOnMacOs(pluginPath: string) {
39
+ const homebrewQuery = await testSpawn('which homebrew');
40
+ if (homebrewQuery.status !== SpawnStatus.SUCCESS) {
41
+ await PluginTester.install(pluginPath, [{ type: 'homebrew', os: [ResourceOs.MACOS] }])
42
+ }
43
+ },
44
+
45
+ async ensureXcodeInstalledOnMacOs(pluginPath: string) {
46
+ const xcodeQuery = await testSpawn('xcodebuild -version');
47
+ if (xcodeQuery.status !== SpawnStatus.SUCCESS) {
48
+ await PluginTester.install(pluginPath, [{ type: 'xcode-tools', os: [ResourceOs.MACOS] }])
49
+ }
50
+ },
51
+
52
+
53
+ getShell(): 'bash' | 'zsh' {
54
+ const shell = process.env.SHELL || '';
55
+
56
+ if (shell.includes('bash')) {
57
+ return 'bash';
58
+ }
59
+
60
+ if (shell.includes('zsh')) {
61
+ return 'zsh';
62
+ }
63
+
64
+ // Default to bash for tests
65
+ return 'bash';
66
+ },
67
+
68
+ /**
69
+ * Get the primary shell rc file path
70
+ */
71
+ getPrimaryShellRc(): string {
72
+ const shell = TestUtils.getShell();
73
+ const homeDir = os.homedir();
74
+
75
+ if (shell === 'bash') {
76
+ return path.join(homeDir, '.bashrc')
77
+ }
78
+
79
+ if (shell === 'zsh') {
80
+ return path.join(homeDir, '.zshrc');
81
+ }
82
+
83
+ throw new Error('Unsupported shell')
84
+ },
85
+
86
+ /**
87
+ * Get the source command for the shell rc file
88
+ * Usage: execSync(TestUtils.getSourceCommand())
89
+ */
90
+ getSourceCommand(): string {
91
+ return `source ${TestUtils.getPrimaryShellRc()}`;
92
+ },
93
+
94
+ /**
95
+ * Get shell-specific command to run with sourced environment
96
+ * Usage: execSync(TestUtils.getShellCommand('which brew'))
97
+ */
98
+ getShellCommand(command: string): string {
99
+ return `${TestUtils.getSourceCommand()}; ${command}`;
100
+ },
101
+
102
+ /**
103
+ * Get shell name for execSync shell option
104
+ */
105
+ getShellName(): string {
106
+ return TestUtils.getShell();
107
+ },
108
+
109
+ /**
110
+ * Get interactive shell command
111
+ * Usage: execSync(TestUtils.getInteractiveCommand('my-alias'))
112
+ */
113
+ getInteractiveCommand(command: string): string {
114
+ const shell = TestUtils.getShell();
115
+
116
+ return shell === 'bash'
117
+ ? `bash -i -c "${command}"`
118
+ : `zsh -i -c "${command}"`;
119
+ },
120
+
121
+ /**
122
+ * Get which command output format based on shell
123
+ */
124
+ getAliasWhichCommand(aliasName: string): string {
125
+ const shell = TestUtils.getShell();
126
+
127
+ // zsh outputs: "alias_name: aliased to command"
128
+ // bash outputs: "alias alias_name='command'"
129
+ return shell === 'bash'
130
+ ? `${TestUtils.getShellCommand(`alias ${aliasName}`)}`
131
+ : `${TestUtils.getShellCommand(`which ${aliasName}`)}`;
132
+ },
133
+
134
+ /**
135
+ * Check if running on macOS
136
+ */
137
+ isMacOS(): boolean {
138
+ return os.platform() === 'darwin';
139
+ },
140
+
141
+ /**
142
+ * Check if running on Linux
143
+ */
144
+ isLinux(): boolean {
145
+ return os.platform() === 'linux';
146
+ },
33
147
  };
package/src/utils.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
1
+ import { ResourceConfig, ResourceOs, StringIndexedObject } from 'codify-schemas';
2
+ import os from 'node:os';
2
3
 
3
4
  export function splitUserConfig<T extends StringIndexedObject>(
4
5
  config: ResourceConfig & T
@@ -7,13 +8,35 @@ export function splitUserConfig<T extends StringIndexedObject>(
7
8
  type: config.type,
8
9
  ...(config.name ? { name: config.name } : {}),
9
10
  ...(config.dependsOn ? { dependsOn: config.dependsOn } : {}),
11
+ ...(config.os ? { os: config.os } : {}),
10
12
  };
11
13
 
12
14
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
13
- const { type, name, dependsOn, ...parameters } = config;
15
+ const { type, name, dependsOn, os, ...parameters } = config;
14
16
 
15
17
  return {
16
18
  parameters: parameters as T,
17
19
  coreParameters,
18
20
  };
19
21
  }
22
+
23
+ export function getPlatformOs(): ResourceOs{
24
+ const currOs = os.platform();
25
+ switch (currOs) {
26
+ case 'darwin': {
27
+ return ResourceOs.MACOS;
28
+ }
29
+
30
+ case 'linux': {
31
+ return ResourceOs.LINUX;
32
+ }
33
+
34
+ case 'win32': {
35
+ return ResourceOs.WINDOWS;
36
+ }
37
+
38
+ default: {
39
+ throw new Error(`Unsupported OS: ${currOs}`);
40
+ }
41
+ }
42
+ }
@@ -27,14 +27,14 @@ describe('Plugin tester integration tests', () => {
27
27
 
28
28
  const result = await plugin.initialize();
29
29
  expect(result).toMatchObject({
30
- resourceDefinitions: [
31
- { dependencies: [], type: 'test' },
32
- { dependencies: [], type: 'test2' },
33
- { dependencies: [], type: 'test-uninstall' },
34
- { dependencies: [], type: 'test-modify' },
35
- { dependencies: [], type: 'test-destroy' },
36
- { dependencies: [], type: 'test-destroy-2' }
37
- ]
30
+ resourceDefinitions: expect.arrayContaining([
31
+ expect.objectContaining({ dependencies: [], type: 'test' }),
32
+ expect.objectContaining({ dependencies: [], type: 'test2' }),
33
+ expect.objectContaining({ dependencies: [], type: 'test-uninstall' }),
34
+ expect.objectContaining({ dependencies: [], type: 'test-modify' }),
35
+ expect.objectContaining({ dependencies: [], type: 'test-destroy' }),
36
+ expect.objectContaining({ dependencies: [], type: 'test-destroy-2' }),
37
+ ])
38
38
  })
39
39
  })
40
40
 
@@ -313,6 +313,17 @@ describe('Plugin tester integration tests', () => {
313
313
  }
314
314
  })
315
315
 
316
+ it('Can filter out unsupported configs based on OS', { timeout: 300000 }, async () => {
317
+ await PluginTester.fullTest(pluginPath, [{
318
+ type: 'windows-only',
319
+ }], {
320
+ validatePlan(plan) {
321
+ expect(plan.length).to.eq(0);
322
+ }
323
+ });
324
+ });
325
+
326
+
316
327
 
317
328
  // it('Can uninstall two resources with the same type', async () => {
318
329
  // await plugin.fullTest([{