Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added 'info' task using envinfo #1632

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@
"svg2js": "0.0.4-alpha1",
"tslib": "2.5.2",
"type-fest": "4.14.0",
"zod": "3.23.8"
"zod": "3.23.8",
"envinfo": "7.13.0"
},
"devDependencies": {
"@types/envinfo": "7.8.4"
},
"peerDependencies": {
"@rnv/config-templates": "^1.0.0-rc.21"
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/enums/taskName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export const RnvTaskName = {
link: 'link',
unlink: 'unlink',
publish: 'publish',
status: 'status',
switch: 'switch',
targetLaunch: 'target launch',
targetList: 'target list',
Expand Down
4 changes: 2 additions & 2 deletions packages/engine-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import taskHooksList from './tasks/hooks/taskHooksList';
import taskHooksRun from './tasks/hooks/taskHooksRun';
import taskHooksPipes from './tasks/hooks/taskHooksPipes';
import taskClean from './tasks/global/taskClean';
import taskStatus from './tasks/global/taskStatus';
import taskInfo from './tasks/global/taskInfo';
import taskConfig from './tasks/global/taskConfig';
import taskHelp from './tasks/global/taskHelp';
import taskNew from './tasks/bootstrap/taskNew';
Expand Down Expand Up @@ -63,7 +63,7 @@ const Engine = createRnvEngine({
taskHooksRun,
taskHooksPipes,
taskClean,
taskStatus,
taskInfo,
taskConfig,
taskHelp,
taskNew,
Expand Down
180 changes: 180 additions & 0 deletions packages/engine-core/src/tasks/global/__tests__/taskInfo.test.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ElenaDiachenko consider changing the scope of tests.

instead of testing what could be considered an internal structure of task:

import { _checkAndConfigureTargetSdk, _formatObject, _getCliVersions } from '../taskInfo';

import task itself

import taskInfo from '../taskInfo';

and create test case scenarios around it.

that way what is tested (taskInfo) is treated as blackbox because nobody will use _checkAndConfigureTargetSdk, _formatObject, _getCliVersions directly (those should be non-expoted methods internal to task).
the method you want to test in this case would be taskClean.fn() because that's the public api called by anybody who uses this task

example:
https://github.com/flexn-io/renative/blob/main/packages/engine-core/src/tasks/global/__tests__/taskClean.test.ts#L35

Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { createRnvContext, execCLI, getContext, logDebug } from '@rnv/core';
import semver from 'semver';
import { _checkAndConfigureTargetSdk, _formatObject, _getCliVersions } from '../taskInfo';

jest.mock('@rnv/core');
jest.mock('semver');
jest.mock('envinfo', () => ({
run: jest.fn(),
}));

beforeEach(() => {
createRnvContext();
});

afterEach(() => {
jest.resetAllMocks();
});

describe('_formatObject tests', () => {
it('should format nested arrays correctly', () => {
// GIVEN
const obj = {
key1: {
key2: {
key3: ['item1', 'item2'],
},
},
};
// WHEN
const result = _formatObject(obj);
// THEN
expect(result).toBe('\nkey1:\n key2:\n key3: item1, item2\n');
});
it('should format nested objects correctly', () => {
// GIVEN
const obj = {
key1: {
key2: {
key3: {
version: '1.0.0',
path: '/mocked/path',
},
},
},
};
// WHEN
const result = _formatObject(obj);
// THEN
expect(result).toBe('\nkey1:\n key2:\n key3: 1.0.0 - /mocked/path\n');
});
});

describe('_getCliVersions tests', () => {
it('should add CLI versions to parsedInfo', async () => {
// GIVEN
const ctx = getContext();
const cliVersionOutput = '1.0.0';
const parsedInfo: { CLI?: any } = {};

ctx.cli = {
webosAres: '/path/to/webosAres',
tizen: '/path/to/tizen',
};

jest.mocked(execCLI).mockResolvedValue(cliVersionOutput);
semver.coerce = jest.fn().mockReturnValue({ version: '1.0.0' });
//WHEN
await _getCliVersions(parsedInfo);
//THEN
expect(execCLI).toHaveBeenCalledTimes(2);
expect(parsedInfo).toHaveProperty('CLI');
expect(parsedInfo.CLI).toHaveProperty('WEBOS CLI', { version: '1.0.0', path: '/path/to/webosAres' });
expect(parsedInfo.CLI).toHaveProperty('TIZEN CLI', { version: '1.0.0', path: '/path/to/tizen' });
});
it('should not add CLI versions if no cli is present in context', async () => {
// GIVEN
const parsedInfo: { CLI?: any } = {};
//WHEN
await _getCliVersions(parsedInfo);
//THEN
expect(execCLI).not.toHaveBeenCalled();
expect(parsedInfo.CLI).toBeUndefined();
});
it('should add only available CLI versions if one cli is present in context', async () => {
// GIVEN
const ctx = getContext();
const cliVersionOutput = '1.0.0';
const parsedInfo: { CLI?: any } = {};

ctx.cli = {
webosAres: '/path/to/webosAres',
};
jest.mocked(execCLI).mockResolvedValue(cliVersionOutput);
semver.coerce = jest.fn().mockReturnValue({ version: '1.0.0' });
//WHEN
await _getCliVersions(parsedInfo);
//THEN
expect(execCLI).toHaveBeenCalledTimes(1);
expect(parsedInfo).toHaveProperty('CLI');
expect(parsedInfo.CLI).toHaveProperty('WEBOS CLI', { version: '1.0.0', path: '/path/to/webosAres' });
expect(parsedInfo.CLI).not.toHaveProperty('TIZEN CLI');
});

it('should handle errors properly', async () => {
// GIVEN
const ctx = getContext();
const parsedInfo: { CLI?: any } = {};
ctx.cli = {
webosAres: '/path/to/webosAres',
tizen: '/path/to/tizen',
};

const error = new Error('Test error');

jest.mocked(execCLI).mockRejectedValue(error);
semver.coerce = jest.fn().mockReturnValue(null);

//WHEN
await _getCliVersions(parsedInfo);
//THEN
expect(execCLI).toHaveBeenCalledTimes(2);
expect(parsedInfo.CLI).toBeUndefined();
expect(logDebug).toHaveBeenCalledTimes(2);
expect(logDebug).toHaveBeenLastCalledWith(`Error getting version for TIZEN CLI: `, error);
});
});

describe('_checkAndConfigureTargetSdk tests', () => {
beforeEach(() => {
jest.resetModules();
});
// GIVEN

it('should call the SDK module function', async () => {
// GIVEN
const mockConfigureFunction = jest.fn();
jest.mock(
`@rnv/mockModule`,
() => ({
mockConfigureFunction,
}),
{ virtual: true }
);
// WHEN
await _checkAndConfigureTargetSdk('mockModule', 'mockConfigureFunction');

//THEN
expect(mockConfigureFunction).toHaveBeenCalled();
});

it('should handle error and log it if module is not found', async () => {
// GIVEN
const moduleName = 'nonExistentModule';
const configureFunction = 'checkAndConfigureMockSdks';

//WHEN
await _checkAndConfigureTargetSdk(moduleName, configureFunction);

// THEN
expect(logDebug).toHaveBeenCalledTimes(1);
expect(logDebug).toHaveBeenCalledWith(
`Error configuring ${moduleName} SDK: `,
expect.objectContaining({
message: expect.stringContaining(`Cannot find module '@rnv/${moduleName}'`),
})
);
});
it('should handle error and log it if the configure function throws an error', async () => {
jest.mock(
'@rnv/mockModule',
() => ({
mockConfigureFunction: jest.fn().mockRejectedValue(new Error('Configuration error')),
}),
{ virtual: true }
);
await _checkAndConfigureTargetSdk('mockModule', 'mockConfigureFunction');

expect(logDebug).toHaveBeenCalledWith('Error configuring mockModule SDK: ', new Error('Configuration error'));
});
});
101 changes: 101 additions & 0 deletions packages/engine-core/src/tasks/global/taskInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { createTask, RnvTaskName, getContext, execCLI, logError, logDebug, logToSummary } from '@rnv/core';
import envinfo from 'envinfo';
import semver from 'semver';

export default createTask({
description: 'Get relevant version info about OS, toolchain and libraries',
fn: async () => {
const parsedInfo = await _getEnvironmentInfo();
await _checkAndConfigureSdks();
await _getCliVersions(parsedInfo);
return logToSummary(_formatObject(parsedInfo));
},
task: RnvTaskName.info,
isGlobalScope: true,
});

export const _checkAndConfigureSdks = async () => {
const moduleConfigs = [
{ moduleName: 'sdk-tizen', configureFunction: 'checkAndConfigureTizenSdks' },
{ moduleName: 'sdk-webos', configureFunction: 'checkAndConfigureWebosSdks' },
];
for (const config of moduleConfigs) {
const { moduleName, configureFunction } = config;
await _checkAndConfigureTargetSdk(moduleName, configureFunction);
}
};

export const _checkAndConfigureTargetSdk = async (moduleName: string, configureFunction: string): Promise<void> => {
try {
const SDKModule = require(`@rnv/${moduleName}`);
await SDKModule[configureFunction]();
} catch (e) {
logDebug(`Error configuring ${moduleName} SDK: `, e);
}
};

export const _getCliVersions = async (parsedInfo: any) => {
const c = getContext();
const cliVersions: { [key: string]: { version: string; path: string } } = {};

const addCliVersion = async (cli: string, command: string, path: string, cliName: string) => {
try {
const cliVersionOutput = await execCLI(cli, command);
const cliVersionNumber = semver.coerce(cliVersionOutput)?.version;
if (cliVersionNumber) {
cliVersions[`${cliName.replace('-', ' ').toUpperCase()}`] = { version: cliVersionNumber, path };
}
} catch (e) {
logDebug(`Error getting version for ${cliName}: `, e);
}
};

if (c.cli.webosAres) {
await addCliVersion('webosAres', '--version', c.cli.webosAres, 'WEBOS CLI');
}
if (c.cli.tizen) {
await addCliVersion('tizen', 'version', c.cli.tizen, 'TIZEN CLI');
}
if (Object.keys(cliVersions).length) {
parsedInfo.CLI = cliVersions;
}
};
export const _formatObject = (obj: any, indent = 0) => {
let formattedString = '';
if (indent === 0) formattedString += '\n';
for (const key in obj) {
if (obj[key] && typeof obj[key] === 'object' && obj[key].version) {
formattedString +=
' '.repeat(indent) + `${key}: ${obj[key].version} ${obj[key].path ? `- ${obj[key].path}` : ''}\n`;
} else if (Array.isArray(obj[key])) {
formattedString += ' '.repeat(indent) + `${key}: ${obj[key].join(', ')}\n`;
} else if (typeof obj[key] === 'object') {
formattedString += ' '.repeat(indent) + `${key}:\n`;
formattedString += _formatObject(obj[key], indent + 2);
} else {
formattedString += ' '.repeat(indent) + `${key}: ${obj[key]}\n`;
}
}
return formattedString;
};

const _getEnvironmentInfo = async () => {
try {
const output = await envinfo.run(
{
System: ['OS', 'CPU', 'Memory', 'Shell'],
Binaries: ['Node', 'Yarn', 'npm', 'Watchman'],
Managers: ['CocoaPods'],
Languages: ['Ruby', 'Java'],
IDEs: ['Xcode', 'Android Studio'],
SDKs: ['iOS SDK', 'Android SDK'],
npmPackages: ['react', 'react-native', '@react-native-community/cli', 'rnv'],
npmGlobalPackages: ['*react-native*', 'rnv', 'lerna'],
},
{ json: true, duplicates: false }
);
return JSON.parse(output);
} catch (e) {
logError(e);
}
};
10 changes: 0 additions & 10 deletions packages/engine-core/src/tasks/global/taskStatus.ts

This file was deleted.

Loading
Loading