diff --git a/packages/engine-core/src/tasks/workspace/taskWorkspaceConfigure.ts b/packages/engine-core/src/tasks/workspace/taskWorkspaceConfigure.ts index 3d5235da6..8164749c9 100644 --- a/packages/engine-core/src/tasks/workspace/taskWorkspaceConfigure.ts +++ b/packages/engine-core/src/tasks/workspace/taskWorkspaceConfigure.ts @@ -37,7 +37,17 @@ export default createTask({ copyFileSync(oldGlobalConfigPath, paths.workspace.config); } else { logInfo(`${paths.workspace.dir}/${RnvFileName.renative} file missing! Creating one for you...`); - writeFileSync(paths.workspace.config, '{}'); + const defaultWorkspaceCnf = { + sdks: { + ANDROID_SDK: '/Users//Library/Android/sdk', + ANDROID_NDK: '/Users//Library/Android/sdk/ndk-bundle', + IOS_SDK: 'No need. Just install Xcode', + TIZEN_SDK: '/Users//tizen-studio', + WEBOS_SDK: '/Users//Library/webOS_TV_SDK', + KAIOS_SDK: '/Applications/Kaiosrt.app', + }, + }; + writeFileSync(paths.workspace.config, JSON.stringify(defaultWorkspaceCnf, null, 2)); } } diff --git a/packages/sdk-android/src/runner.ts b/packages/sdk-android/src/runner.ts index 386527fec..6159223ae 100644 --- a/packages/sdk-android/src/runner.ts +++ b/packages/sdk-android/src/runner.ts @@ -61,7 +61,7 @@ import { } from './deviceManager'; import { ANDROID_COLORS, ANDROID_STRINGS, ANDROID_STYLES, CLI_ANDROID_ADB } from './constants'; import { runReactNativeAndroid, packageReactNativeAndroid, generateEnvVarsFile } from '@rnv/sdk-react-native'; -import { getEntryFile } from '@rnv/sdk-utils'; +import { getEntryFile, updateDefaultTargets } from '@rnv/sdk-utils'; import { Context, getContext } from './getContext'; export const packageAndroid = async () => { @@ -120,6 +120,10 @@ export const getAndroidDeviceToRunOn = async () => { choices, }); if (chosenTarget) { + // update defaultTarget + if (!target) { + await updateDefaultTargets(c, chosenTarget); + } const dev = activeDevices.find((d) => d.name === chosenTarget); if (dev) return dev; diff --git a/packages/sdk-apple/src/__tests__/runner.test.ts b/packages/sdk-apple/src/__tests__/runner.test.ts index ec8c99c40..8c284e3b7 100644 --- a/packages/sdk-apple/src/__tests__/runner.test.ts +++ b/packages/sdk-apple/src/__tests__/runner.test.ts @@ -2,6 +2,7 @@ import { inquirerPrompt, getContext, createRnvContext, logSuccess } from '@rnv/c import type { PromptParams } from '@rnv/core'; import { getIosDeviceToRunOn } from '../runner'; import { getAppleDevices } from '../deviceManager'; +import { updateDefaultTargets } from '@rnv/sdk-utils'; const simJson = [ { @@ -33,6 +34,7 @@ const devicesJson = [ ]; jest.mock('@rnv/core'); +jest.mock('@rnv/sdk-utils'); jest.mock('../deviceManager'); jest.mock('chalk', () => ({ bold: { @@ -97,22 +99,17 @@ describe('getIosDeviceToRunOn', () => { [name as string]: true, }; } - if (type === 'list') { - // Testing the addition of global/project value should be handled in another UT - if (choices?.includes("Don't update")) { - const choiceIndex = choices.findIndex((c) => c === "Don't update"); - return { - [name as string]: - (choices![choiceIndex] as { name: string; value: any }).value || choices![choiceIndex], - }; - } // By default first value returned (aka the first simulator from the list in this case) return { [name as string]: (choices![0] as { name: string; value: any }).value || choices![0], }; } }); + jest.mocked(updateDefaultTargets).mockImplementation(async (ctx, currentTarget) => { + if (!ctx.platform) return; + ctx.runtime.target = currentTarget; + }); // WHEN const deviceArgs = await getIosDeviceToRunOn(ctx); //THEN @@ -139,14 +136,19 @@ describe('getIosDeviceToRunOn', () => { ctx.files.workspace.config = {}; ctx.runtime.target = 'iPhone 14'; jest.mocked(getAppleDevices).mockResolvedValueOnce(simJson); - jest.mocked(inquirerPrompt).mockImplementation(async ({ type, name, choices }: PromptParams) => { - if (type === 'list' && choices?.includes('Update global default target for platform ios')) { - return { [name as string]: 'Update global default target for platform ios' }; - } + jest.mocked(inquirerPrompt).mockImplementation(async ({ name, choices }: PromptParams) => { return { [name as string]: (choices![0] as { name: string; value: any }).value || choices![0], }; }); + jest.mocked(updateDefaultTargets).mockImplementation(async (ctx, currentTarget) => { + if (!ctx.platform) return; + const configGlobal = ctx.files.workspace.config || {}; + configGlobal.defaultTargets = configGlobal.defaultTargets || {}; + configGlobal.defaultTargets[ctx.platform] = currentTarget; + ctx.files.workspace.config = configGlobal; + ctx.runtime.target = currentTarget; + }); // WHEN const deviceArgs = await getIosDeviceToRunOn(ctx); // THEN diff --git a/packages/sdk-apple/src/runner.ts b/packages/sdk-apple/src/runner.ts index b44c15a01..6331bab9b 100644 --- a/packages/sdk-apple/src/runner.ts +++ b/packages/sdk-apple/src/runner.ts @@ -45,6 +45,7 @@ import { import { registerDevice } from './fastlane'; import { Context, getContext } from './getContext'; import { parsePrivacyManifest } from './privacyManifestParser'; +import { updateDefaultTargets } from '@rnv/sdk-utils'; export const packageBundleForXcode = () => { return packageReactNativeIOS(); @@ -175,43 +176,7 @@ export const getIosDeviceToRunOn = async (c: Context) => { })), }); desiredSim = currentTarget; - const localOverridden = !!c.files.project.configLocal?.defaultTargets?.[c.platform]; - - const actionLocalUpdate = `Update ${chalk().green('project')} default target for platform ${c.platform}`; - const actionGlobalUpdate = `Update ${chalk().green('global')}${ - localOverridden ? ` and ${chalk().green('project')}` : '' - } default target for platform ${c.platform}`; - const actionNoUpdate = "Don't update"; - - const { chosenAction } = await inquirerPrompt({ - message: 'What to do next?', - type: 'list', - name: 'chosenAction', - choices: [actionLocalUpdate, actionGlobalUpdate, actionNoUpdate], - warningMessage: `Your default target for platform ${c.platform} is set to ${c.runtime.target}.`, - }); - - c.runtime.target = currentTarget.name; - - if (chosenAction === actionLocalUpdate || (chosenAction === actionGlobalUpdate && localOverridden)) { - const configLocal = c.files.project.configLocal || {}; - if (!configLocal.defaultTargets) configLocal.defaultTargets = {}; - configLocal.defaultTargets[c.platform] = currentTarget.name; - - c.files.project.configLocal = configLocal; - writeFileSync(c.paths.project.configLocal, configLocal); - } - - if (chosenAction === actionGlobalUpdate) { - const configGlobal = c.files.workspace.config; - if (configGlobal) { - if (!configGlobal.defaultTargets) configGlobal.defaultTargets = {}; - configGlobal.defaultTargets[c.platform] = currentTarget.name; - - c.files.workspace.config = configGlobal; - writeFileSync(c.paths.workspace.config, configGlobal); - } - } + await updateDefaultTargets(c, currentTarget.name); } if (!desiredSim?.isDevice) { const target = c.runtime.target?.replace(/(\s+)/g, '\\$1'); diff --git a/packages/sdk-tizen/src/__tests__/deviceManager.test.ts b/packages/sdk-tizen/src/__tests__/deviceManager.test.ts index 29573b6ac..b0c9fccbd 100644 --- a/packages/sdk-tizen/src/__tests__/deviceManager.test.ts +++ b/packages/sdk-tizen/src/__tests__/deviceManager.test.ts @@ -19,6 +19,7 @@ const ERROR_MSG = { }; jest.mock('@rnv/core'); +jest.mock('@rnv/sdk-utils'); beforeEach(() => { jest.clearAllMocks(); @@ -135,6 +136,7 @@ describe('launchTizenTarget', () => { value: '111.111.111:11111', }, ], + default: 'emulatorTizen', }); expect(executeAsync).toHaveBeenCalledWith( 'tizen-emulator launch --name emulatorTizen', @@ -163,6 +165,7 @@ describe('launchTizenTarget', () => { name: 'chosenEmulator', type: 'list', message: 'which emulator would you like to launch?', + default: 'emulatorTizen', choices: [ { key: 'emulatorTizen', @@ -202,6 +205,7 @@ describe('launchTizenTarget', () => { { key: 'emulatorTizen', name: 'emulatorTizen', value: 'emulatorTizen' }, { key: '111.111.111.111:11111', name: '111.111.111.111:11111', value: '111.111.111.111:11111' }, ], + default: 'emulatorTizen', }); expect(result).toBe(true); }); @@ -242,6 +246,7 @@ describe('launchTizenTarget', () => { value: '111.111.111.111:11111', }, ], + default: 'emulatorTizen', }); expect(executeAsync).toHaveBeenCalledWith( 'tizen-emulator launch --name emulatorTizen', diff --git a/packages/sdk-tizen/src/deviceManager.ts b/packages/sdk-tizen/src/deviceManager.ts index 5ca657ef2..4a3fc48ed 100644 --- a/packages/sdk-tizen/src/deviceManager.ts +++ b/packages/sdk-tizen/src/deviceManager.ts @@ -24,6 +24,7 @@ import { import { CLI_SDB_TIZEN, CLI_TIZEN, CLI_TIZEN_EMULATOR } from './constants'; import { TizenDevice, TizenSecurityConfig } from './types'; +import { updateDefaultTargets } from '@rnv/sdk-utils'; const xml2js = require('xml2js'); @@ -72,7 +73,11 @@ const formatXMLObject = ( return {}; }; -export const launchTizenTarget = async (name: string | true, hideDevices?: boolean): Promise => { +export const launchTizenTarget = async ( + name: string | true, + hideDevices?: boolean, + updateDefault = false +): Promise => { const c = getContext(); logDefault(`launchTizenTarget:${name}`); if (name === true) { @@ -81,7 +86,7 @@ export const launchTizenTarget = async (name: string | true, hideDevices?: boole const devices_lines = devices.split('\n'); const devicesArr = devices_lines.slice(1).map((line: string) => line.split(' ')[0]); // devices array with only their ip - const allDownloadedEmulators = emulators.split('\n'); // all tizen, tizenwatch and tizenmobile emulators + const allDownloadedEmulators = emulators.split('\n').map((em) => em.trim()); // all tizen, tizenwatch and tizenmobile emulators const specificEmulators = await getEmulatorType(allDownloadedEmulators, c.platform as string); const specificDevices = await getDeviceType(devicesArr, c.platform as string); @@ -104,9 +109,19 @@ export const launchTizenTarget = async (name: string | true, hideDevices?: boole ? 'which emulator would you like to launch?' : 'which emulator or device would you like to launch?', choices, + default: choices.find((it) => it.key.includes('samsung'))?.key || choices[0].key, }); + if ( + chosenEmulator && + (c.files.project.configLocal?.defaultTargets?.[c.platform!] || + c.files.workspace.config?.defaultTargets?.[c.platform!] !== chosenEmulator) + ) { + // update defaultTarget in .rnv/renative.json + await updateDefaultTargets(c, chosenEmulator); + } name = chosenEmulator; + c.runtime.target = chosenEmulator; } if (name && typeof name === 'string') { const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}:\d{1,5}$/; @@ -118,11 +133,16 @@ export const launchTizenTarget = async (name: string | true, hideDevices?: boole return new Promise(() => logInfo('Device is launched.')); } try { - await executeAsync( - `${c.cli[CLI_TIZEN_EMULATOR]} launch --name ${name}`, - ExecOptionsPresets.SPINNER_FULL_ERROR_SUMMARY - ); - return true; + if (updateDefault) { + await runTizenSimOrDevice(); + return true; + } else { + await executeAsync( + `${c.cli[CLI_TIZEN_EMULATOR]} launch --name ${name}`, + ExecOptionsPresets.SPINNER_FULL_ERROR_SUMMARY + ); + return true; + } } catch (e) { if (typeof e === 'string') { if (e.includes(ERROR_MSG.UNKNOWN_VM)) { @@ -428,7 +448,7 @@ export const runTizenSimOrDevice = async () => { if (!tId) return Promise.reject(`Tizen platform requires "id" filed in platforms.tizen`); const askForEmulator = async () => { if (!target) { - launchTizenTarget(true); + launchTizenTarget(true, undefined, true); return; } const { startEmulator } = await inquirerPrompt({ @@ -439,7 +459,7 @@ export const runTizenSimOrDevice = async () => { if (startEmulator) { isRunningEmulator = true; - const defaultTarget = c.files.workspace.config?.defaultTargets?.[platform]; + const defaultTarget = c.runtime.target; if (!defaultTarget) { logError('No default target found for tizen. please provide one using -t option'); return; @@ -482,10 +502,10 @@ Please create one and then edit the default target from ${c.paths.workspace.dir} hasDevice = true; } catch (e) { if (typeof e === 'string' && e.includes('No device matching')) { - if (target) { + if (c.runtime.target) { isRunningEmulator = true; - await launchTizenTarget(target); - hasDevice = await _waitForEmulatorToBeReady(target); + await launchTizenTarget(c.runtime.target); + hasDevice = await _waitForEmulatorToBeReady(c.runtime.target); } else { return Promise.reject('Not target specified. (-t)'); } @@ -512,7 +532,7 @@ Please create one and then edit the default target from ${c.paths.workspace.dir} logError(err); } - if (!target) { + if (!c.runtime.target) { return Promise.reject('Not target specified. (-t)'); } @@ -520,8 +540,8 @@ Please create one and then edit the default target from ${c.paths.workspace.dir} await launchTizenTarget(true); } else { isRunningEmulator = true; - await launchTizenTarget(target); - hasDevice = await _waitForEmulatorToBeReady(target); + await launchTizenTarget(c.runtime.target); + hasDevice = await _waitForEmulatorToBeReady(c.runtime.target); } } @@ -534,7 +554,10 @@ Please create one and then edit the default target from ${c.paths.workspace.dir} if (platform !== 'tizenwatch' && platform !== 'tizenmobile' && hasDevice) { // change id for for emulator because tizen 8+ fails to run app with - await execCLI(CLI_TIZEN, `run -p ${isRunningEmulator ? tId.split('.')[0] : tId} -t ${deviceID}`); + await execCLI( + CLI_TIZEN, + `run -p ${isRunningEmulator && !deviceID.includes('samsung') ? tId.split('.')[0] : tId} -t ${deviceID}` + ); } else if ((platform === 'tizenwatch' || platform === 'tizenmobile') && hasDevice) { const packageID = tId.split('.'); await execCLI(CLI_TIZEN, `run -p ${packageID[0]} -t ${deviceID}`); @@ -566,8 +589,9 @@ Please create one and then edit the default target from ${c.paths.workspace.dir} // try to launch it, see if it's a emulator that's not started yet isRunningEmulator = true; await launchTizenTarget(target); - await _waitForEmulatorToBeReady(target); - deviceID = target; + const currentTarget = c.runtime.target || target; + await _waitForEmulatorToBeReady(currentTarget); + deviceID = currentTarget; return continueLaunching(); } catch (e) { return askForEmulator(); diff --git a/packages/sdk-utils/src/target.ts b/packages/sdk-utils/src/target.ts index 64974e030..159707b92 100644 --- a/packages/sdk-utils/src/target.ts +++ b/packages/sdk-utils/src/target.ts @@ -1,4 +1,4 @@ -import { getContext, inquirerPrompt } from '@rnv/core'; +import { RnvContext, chalk, getContext, inquirerPrompt, writeFileSync } from '@rnv/core'; export const getTargetWithOptionalPrompt = async () => { const ctx = getContext(); @@ -31,3 +31,46 @@ export const getTargetWithOptionalPrompt = async () => { } return target; }; + +export const updateDefaultTargets = async (c: RnvContext, currentTarget: string) => { + if (!c.platform) return; + const localOverridden = !!c.files.project.configLocal?.defaultTargets?.[c.platform]; + const defaultTarget = c.runtime.target; + const actionLocalUpdate = `Update ${chalk().green('project')} default target for platform ${c.platform}`; + const actionGlobalUpdate = `Update ${chalk().green('global')}${ + localOverridden ? ` and ${chalk().green('project')}` : '' + } default target for platform ${c.platform}`; + const actionNoUpdate = "Don't update"; + + const { chosenAction } = await inquirerPrompt({ + message: 'What to do next?', + type: 'list', + name: 'chosenAction', + choices: [actionLocalUpdate, actionGlobalUpdate, actionNoUpdate], + warningMessage: `Your default target for platform ${c.platform} is ${ + !defaultTarget ? 'not defined' : `set to ${defaultTarget}` + }.`, + }); + + c.runtime.target = currentTarget; + + if (chosenAction === actionLocalUpdate || (chosenAction === actionGlobalUpdate && localOverridden)) { + const configLocal = c.files.project.configLocal || {}; + if (!configLocal.defaultTargets) configLocal.defaultTargets = {}; + configLocal.defaultTargets[c.platform] = currentTarget; + + c.files.project.configLocal = configLocal; + writeFileSync(c.paths.project.configLocal, JSON.stringify(configLocal, null, 2)); + } + + if (chosenAction === actionGlobalUpdate) { + const configGlobal = c.files.workspace.config; + if (configGlobal) { + if (!configGlobal.defaultTargets) configGlobal.defaultTargets = {}; + configGlobal.defaultTargets[c.platform] = currentTarget; + + c.files.workspace.config = configGlobal; + writeFileSync(c.paths.workspace.config, JSON.stringify(configGlobal, null, 2)); + } + } +}; diff --git a/packages/sdk-webos/src/deviceManager.ts b/packages/sdk-webos/src/deviceManager.ts index 85bf59ff3..f58be5d75 100644 --- a/packages/sdk-webos/src/deviceManager.ts +++ b/packages/sdk-webos/src/deviceManager.ts @@ -1,4 +1,5 @@ import path from 'path'; +import { isString } from 'lodash'; import { getRealPath, fsReadFileSync, @@ -21,7 +22,6 @@ import { getAppFolder, getContext, logError, - writeFileSync, } from '@rnv/core'; import { WebosDevice } from './types'; import { @@ -33,7 +33,7 @@ import { CLI_WEBOS_ARES_DEVICE_INFO, } from './constants'; import semver from 'semver'; -import { isUrlLocalhost } from '@rnv/sdk-utils'; +import { isUrlLocalhost, updateDefaultTargets } from '@rnv/sdk-utils'; export const launchWebOSimulator = async (target: string | boolean) => { logTask('launchWebOSimulator', `${target}`); @@ -132,6 +132,7 @@ const parseDevices = (c: RnvContext, devicesResponse: string): Promise { logDefault('launchAppOnSimulator'); const defaultTarget = c.runtime.target; + const { target } = c.program.opts(); const webosSdkPath = getRealPath(c.buildConfig?.sdks?.WEBOS_SDK); if (!webosSdkPath) { @@ -164,18 +165,32 @@ const launchAppOnSimulator = async (c: RnvContext, appPath: string) => { selectedSimulator = defaultTarget; logInfo(`Found default simulator ${chalk().bold.white(selectedSimulator)} in ${simulatorDirPath}`); } else { - if (availableEmulatorVersions.length > 1) { - ({ selectedSimulator } = await inquirerPrompt({ - name: 'selectedSimulator', - type: 'list', - choices: availableEmulatorVersions, - message: `Found several installed simulators. Choose which one to use:`, - })); - } else { - selectedSimulator = availableEmulatorVersions[0]; - logInfo(`Found simulator ${chalk().bold.white(selectedSimulator)} in ${simulatorDirPath}`); + if (availableEmulatorVersions.length) { + if (isString(target) && availableEmulatorVersions.includes(target)) { + ({ selectedSimulator } = await inquirerPrompt({ + name: 'selectedSimulator', + type: 'list', + choices: availableEmulatorVersions, + message: `Found installed simulators. Choose which one to use:`, + })); + } else { + logInfo( + `The target ${ + !target || target === true + ? 'is not specified' + : `is specified, but no such target is available: ${chalk().magenta(target)}` + }. Will try to find available one` + ); + ({ selectedSimulator } = await inquirerPrompt({ + name: 'selectedSimulator', + type: 'list', + choices: availableEmulatorVersions, + message: `Found installed simulators. Choose which one to use:`, + })); + } } - await _updateDefaultTargets(c, selectedSimulator); + + await updateDefaultTargets(c, selectedSimulator); } const regex = /\d+(\.\d+)?/g; @@ -194,31 +209,6 @@ const launchAppOnSimulator = async (c: RnvContext, appPath: string) => { ); }; -const _updateDefaultTargets = async (c: RnvContext, selectedSimulator: string) => { - const defaultTarget = c.runtime.target; - const { confirm } = await inquirerPrompt({ - type: 'confirm', - name: 'confirm', - message: `Your default target for platform ${c.platform} is ${ - !defaultTarget ? 'not defined' : `set to ${defaultTarget}` - }. Do you want to ${!defaultTarget ? 'set' : `update `} it to ${selectedSimulator} `, - }); - if (!confirm) return; - - const workspaceConfig = c.files.workspace.config; - - if (workspaceConfig && c.platform) { - if (!workspaceConfig.defaultTargets) workspaceConfig.defaultTargets = {}; - - workspaceConfig.defaultTargets[c.platform] = selectedSimulator; - - c.files.workspace.config = workspaceConfig; - writeFileSync(c.paths.workspace.config, workspaceConfig); - } - logInfo( - `Your default target for platform ${c.platform} has been updated successfully in ${c.paths.workspace.config}` - ); -}; // Used for actual devices const installAndLaunchApp = async (target: string, appPath: string, tId: string) => { try { @@ -274,7 +264,7 @@ export const listWebOSTargets = async () => { export const runWebosSimOrDevice = async () => { const c = getContext(); - const { device } = c.program.opts(); + const { device, target } = c.program.opts(); const platDir = getAppFolder(); if (!platDir) { @@ -304,8 +294,12 @@ export const runWebosSimOrDevice = async () => { // List all devices const devicesResponse = await execCLI(CLI_WEBOS_ARES_DEVICE_INFO, '-D'); + const devices = await parseDevices(c, devicesResponse); const activeDevices = devices.filter((d) => d.active); + const target_name = devices.find((device) => { + return device.device.includes(target) || device.name.includes(target); + })?.name; if (device) { // Running on a device @@ -346,7 +340,7 @@ export const runWebosSimOrDevice = async () => { const tv = actualDevices[0]; return installAndLaunchApp(tv.name, appPath, tId); } - } else if (!c.program.opts().target) { + } else if (!target) { // No target specified if (activeDevices.length === 1) { // One device present @@ -371,13 +365,15 @@ export const runWebosSimOrDevice = async () => { return logError(`${error}`); } } + } else if (target && !target_name) { + try { + return await launchAppOnSimulator(c, appLocation); + } catch (error) { + return logError(`${error}`); + } } else { - const target_name = devices.find((device) => { - return device.device.includes(c.program.opts().target) || device.name.includes(c.program.opts().target); - })?.name; - if (!target_name) { - return Promise.reject(`Target ${c.program.opts().target} doesn't exist.`); + return Promise.reject(`Target ${target} doesn't exist.`); } return installAndLaunchApp(target_name, appPath, tId); }