From 51b789dcc729c749e2270364618fef2bd927591a Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Wed, 18 Jun 2025 01:42:49 +0530 Subject: [PATCH 01/10] Feat: Enhanced TS support for browserstack-cypress-cli --- bin/helpers/readCypressConfigUtil.js | 83 ++++++++++++++++++++++++---- package.json | 9 +-- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index bdf556bc..f77164a6 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -13,6 +13,68 @@ exports.detectLanguage = (cypress_config_filename) => { return constants.CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension) ? extension : 'js' } +function generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, complied_js_dir, cypress_config_filepath) { + const working_dir = path.dirname(cypress_config_filepath); + const typescript_path = path.join(bstack_node_modules_path, 'typescript', 'bin', 'tsc'); + const tsc_alias_path = path.join(bstack_node_modules_path, 'tsc-alias', 'dist', 'bin', 'index.js'); + const tsConfigFilePath = bsConfig.run_settings.ts_config_file_path; + + // Prepare base temp tsconfig + const tempTsConfig = { + compilerOptions: { + "outDir": `./${path.basename(complied_js_dir)}`, // Add ./ prefix for consistency + "listEmittedFiles": true, + "allowSyntheticDefaultImports": true, + "module": "commonjs", + "declaration": false, + "baseUrl": ".", // Default fallback baseUrl + "skipLibCheck": true + }, + include: [cypress_config_filepath] + }; + + // Inject paths and baseUrl from original tsconfig if available + if (tsConfigFilePath && fs.existsSync(tsConfigFilePath) && path.extname(tsConfigFilePath).toLowerCase() === '.json') { + const tsConfig = JSON.parse(fs.readFileSync(tsConfigFilePath, 'utf8')); + if (tsConfig.compilerOptions) { + if (tsConfig.compilerOptions.baseUrl) { + // Use the directory containing the original tsconfig as baseUrl + tempTsConfig.compilerOptions.baseUrl = path.dirname(tsConfigFilePath); + } else { + logger.warn(`tsconfig at ${tsConfigFilePath} does not define baseUrl, defaulting to "."`); + } + if (tsConfig.compilerOptions.paths) { + tempTsConfig.compilerOptions.paths = tsConfig.compilerOptions.paths; + } + } + } else { + logger.warn(`tsconfig file not found or invalid: ${tsConfigFilePath}`); + } + + // Write the temporary tsconfig + const tempTsConfigPath = path.join(working_dir, 'tsconfig.singlefile.tmp.json'); + fs.writeFileSync(tempTsConfigPath, JSON.stringify(tempTsConfig, null, 2)); + logger.info(`Temporary tsconfig created at: ${tempTsConfigPath}`); + + // Platform-specific command generation + const isWindows = /^win/.test(process.platform); + + if (isWindows) { + // Windows: Use && to chain commands, no space after SET + const setNodePath = isWindows + ? `set NODE_PATH=${nodePath}` + : `NODE_PATH="${nodePath}"`; + + const tscCommand = `${setNodePath} && node "${typescript_path}" --project "${tempTsConfigPath}" && ${setNodePath} && node "${tsc_alias_path}" --project "${tempTsConfigPath}"`; + return { tscCommand, tempTsConfigPath }; + } else { + // Unix/Linux/macOS: Use ; to separate commands or && to chain + const nodePathPrefix = `NODE_PATH=${bsConfig.run_settings.bstack_node_modules_path}`; + const tscCommand = `${nodePathPrefix} node "${typescript_path}" --project "${tempTsConfigPath}" && ${nodePathPrefix} node "${tsc_alias_path}" --project "${tempTsConfigPath}"`; + return { tscCommand, tempTsConfigPath }; + } +} + exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_modules_path) => { const cypress_config_filename = bsConfig.run_settings.cypress_config_filename const working_dir = path.dirname(cypress_config_filepath); @@ -22,19 +84,12 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module } fs.mkdirSync(complied_js_dir, { recursive: true }) - const typescript_path = path.join(bstack_node_modules_path, 'typescript', 'bin', 'tsc') - - let tsc_command = `NODE_PATH=${bstack_node_modules_path} node "${typescript_path}" --outDir "${complied_js_dir}" --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false "${cypress_config_filepath}"` + const { tscCommand, tempTsConfigPath } = generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, complied_js_dir, cypress_config_filepath); - if (/^win/.test(process.platform)) { - tsc_command = `set NODE_PATH=${bstack_node_modules_path}&& node "${typescript_path}" --outDir "${complied_js_dir}" --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false "${cypress_config_filepath}"` - } - - let tsc_output try { - logger.debug(`Running: ${tsc_command}`) - tsc_output = cp.execSync(tsc_command, { cwd: working_dir }) + logger.debug(`Running: ${tscCommand}`) + tsc_output = cp.execSync(tscCommand, { cwd: working_dir }) } catch (err) { // error while compiling ts files logger.debug(err.message); @@ -44,6 +99,12 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module logger.debug(`Saved compiled js output at: ${complied_js_dir}`); logger.debug(`Finding compiled cypress config file in: ${complied_js_dir}`); + // Clean up the temporary tsconfig file + if (fs.existsSync(tempTsConfigPath)) { + fs.unlinkSync(tempTsConfigPath); + logger.info(`Temporary tsconfig file removed: ${tempTsConfigPath}`); + } + const lines = tsc_output.toString().split('\n'); let foundLine = null; for (let i = 0; i < lines.length; i++) { @@ -53,7 +114,7 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module } } if (foundLine === null) { - logger.error(`No compiled cypress config found. There might some error running ${tsc_command} command`) + logger.error(`No compiled cypress config found. There might some error running ${tscCommand} command`) return null } else { const compiled_cypress_config_filepath = foundLine.split('TSFILE: ').pop() diff --git a/package.json b/package.json index 719e663d..a6fdcf1f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "browserstack-local": "1.5.4", "chalk": "4.1.2", "cli-progress": "^3.10.0", + "decompress": "4.2.1", "form-data": "^4.0.0", "fs-extra": "8.1.0", "getmac": "5.20.0", @@ -26,17 +27,17 @@ "git-repo-info": "^2.1.1", "gitconfiglocal": "^2.1.0", "glob": "^7.2.0", - "mocha": "^10.2.0", "mkdirp": "1.0.4", + "mocha": "^10.2.0", "node-ipc": "9.1.1", "table": "5.4.6", + "tsc-alias": "^1.8.16", + "unzipper": "^0.12.3", "update-notifier": "7.0.0", "uuid": "8.3.2", "windows-release": "^5.1.0", "winston": "2.4.4", - "yargs": "14.2.3", - "decompress": "4.2.1", - "unzipper": "^0.12.3" + "yargs": "14.2.3" }, "repository": { "type": "git", From 278ed4aff64baf9150dc15b94c46bf8416df7780 Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Wed, 18 Jun 2025 02:01:47 +0530 Subject: [PATCH 02/10] Fix: Remove tsc-alias from browserstack-cli, similar to typscript user need to install --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index a6fdcf1f..0cc5fe2d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "mocha": "^10.2.0", "node-ipc": "9.1.1", "table": "5.4.6", - "tsc-alias": "^1.8.16", "unzipper": "^0.12.3", "update-notifier": "7.0.0", "uuid": "8.3.2", From 63cc8c5ba495c1dc2db762192dee8b36e00efdaf Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Wed, 18 Jun 2025 16:20:32 +0530 Subject: [PATCH 03/10] Debugging: Add info for TSC command output --- bin/helpers/readCypressConfigUtil.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index f77164a6..2d6cccd4 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -105,6 +105,8 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module logger.info(`Temporary tsconfig file removed: ${tempTsConfigPath}`); } + logger.info(tsc_output.toString()); + const lines = tsc_output.toString().split('\n'); let foundLine = null; for (let i = 0; i < lines.length; i++) { From f56064a63d225fb9fc62b4443d73f8cf1e50906c Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Wed, 18 Jun 2025 17:52:33 +0530 Subject: [PATCH 04/10] Fix --- bin/helpers/readCypressConfigUtil.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index 2d6cccd4..e3df7f2d 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -62,8 +62,8 @@ function generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, c if (isWindows) { // Windows: Use && to chain commands, no space after SET const setNodePath = isWindows - ? `set NODE_PATH=${nodePath}` - : `NODE_PATH="${nodePath}"`; + ? `set NODE_PATH=${bstack_node_modules_path}` + : `NODE_PATH="${bstack_node_modules_path}"`; const tscCommand = `${setNodePath} && node "${typescript_path}" --project "${tempTsConfigPath}" && ${setNodePath} && node "${tsc_alias_path}" --project "${tempTsConfigPath}"`; return { tscCommand, tempTsConfigPath }; From 9653cd96d9ff30c8ad2162b72dfbfd06f121f1ea Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Wed, 18 Jun 2025 18:11:38 +0530 Subject: [PATCH 05/10] Fix2 --- bin/helpers/readCypressConfigUtil.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index e3df7f2d..69a2bada 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -22,7 +22,7 @@ function generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, c // Prepare base temp tsconfig const tempTsConfig = { compilerOptions: { - "outDir": `./${path.basename(complied_js_dir)}`, // Add ./ prefix for consistency + "outDir": `${path.basename(complied_js_dir)}`, // Add ./ prefix for consistency "listEmittedFiles": true, "allowSyntheticDefaultImports": true, "module": "commonjs", @@ -65,12 +65,14 @@ function generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, c ? `set NODE_PATH=${bstack_node_modules_path}` : `NODE_PATH="${bstack_node_modules_path}"`; - const tscCommand = `${setNodePath} && node "${typescript_path}" --project "${tempTsConfigPath}" && ${setNodePath} && node "${tsc_alias_path}" --project "${tempTsConfigPath}"`; + const tscCommand = `${setNodePath} && node "${typescript_path}" --project "${tempTsConfigPath}" && ${setNodePath} && node "${tsc_alias_path}" --project "${tempTsConfigPath}" --verbose`; + logger.info(`TypeScript compilation command: ${tscCommand}`); return { tscCommand, tempTsConfigPath }; } else { // Unix/Linux/macOS: Use ; to separate commands or && to chain const nodePathPrefix = `NODE_PATH=${bsConfig.run_settings.bstack_node_modules_path}`; - const tscCommand = `${nodePathPrefix} node "${typescript_path}" --project "${tempTsConfigPath}" && ${nodePathPrefix} node "${tsc_alias_path}" --project "${tempTsConfigPath}"`; + const tscCommand = `${nodePathPrefix} node "${typescript_path}" --project "${tempTsConfigPath}" && ${nodePathPrefix} node "${tsc_alias_path}" --project "${tempTsConfigPath}" --verbose`; + logger.info(`TypeScript compilation command: ${tscCommand}`); return { tscCommand, tempTsConfigPath }; } } From 270f2951b4515bf10acc7d7eaf8584101bd5984a Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Wed, 18 Jun 2025 18:14:27 +0530 Subject: [PATCH 06/10] fix3 --- bin/helpers/readCypressConfigUtil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index 69a2bada..e9398ce4 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -103,7 +103,7 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module // Clean up the temporary tsconfig file if (fs.existsSync(tempTsConfigPath)) { - fs.unlinkSync(tempTsConfigPath); + // fs.unlinkSync(tempTsConfigPath); logger.info(`Temporary tsconfig file removed: ${tempTsConfigPath}`); } From ce8bd83a151336bfbcea730b5676b1824fbea710 Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Thu, 19 Jun 2025 11:09:31 +0530 Subject: [PATCH 07/10] Fix: Working on windows --- bin/helpers/readCypressConfigUtil.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index e9398ce4..ebc6ec54 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -37,13 +37,8 @@ function generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, c if (tsConfigFilePath && fs.existsSync(tsConfigFilePath) && path.extname(tsConfigFilePath).toLowerCase() === '.json') { const tsConfig = JSON.parse(fs.readFileSync(tsConfigFilePath, 'utf8')); if (tsConfig.compilerOptions) { - if (tsConfig.compilerOptions.baseUrl) { - // Use the directory containing the original tsconfig as baseUrl - tempTsConfig.compilerOptions.baseUrl = path.dirname(tsConfigFilePath); - } else { - logger.warn(`tsconfig at ${tsConfigFilePath} does not define baseUrl, defaulting to "."`); - } if (tsConfig.compilerOptions.paths) { + tempTsConfig.compilerOptions.baseUrl = path.dirname(path.resolve(tsConfigFilePath)); tempTsConfig.compilerOptions.paths = tsConfig.compilerOptions.paths; } } @@ -92,6 +87,7 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module try { logger.debug(`Running: ${tscCommand}`) tsc_output = cp.execSync(tscCommand, { cwd: working_dir }) + cp.execSync(tscCommand, { cwd: working_dir }) } catch (err) { // error while compiling ts files logger.debug(err.message); @@ -103,7 +99,7 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module // Clean up the temporary tsconfig file if (fs.existsSync(tempTsConfigPath)) { - // fs.unlinkSync(tempTsConfigPath); + fs.unlinkSync(tempTsConfigPath); logger.info(`Temporary tsconfig file removed: ${tempTsConfigPath}`); } @@ -178,4 +174,4 @@ exports.readCypressConfigFile = (bsConfig) => { fs.rmdirSync(complied_js_dir, { recursive: true }) } } -} +} \ No newline at end of file From df7b87c10c7d3d7a575daebe3740fcc53e491c18 Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Thu, 19 Jun 2025 17:12:12 +0530 Subject: [PATCH 08/10] Fix: Change to nodenext for better moduleResolution --- bin/helpers/readCypressConfigUtil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index ebc6ec54..de4ceeda 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -25,7 +25,7 @@ function generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, c "outDir": `${path.basename(complied_js_dir)}`, // Add ./ prefix for consistency "listEmittedFiles": true, "allowSyntheticDefaultImports": true, - "module": "commonjs", + "module": "nodenext", "declaration": false, "baseUrl": ".", // Default fallback baseUrl "skipLibCheck": true From 36c3dbc20f4231ded3ea28466fd612a7b1a18128 Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Fri, 20 Jun 2025 17:02:10 +0530 Subject: [PATCH 09/10] FIx: Cross OS issue --- bin/helpers/capabilityHelper.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index 425f7e43..f380b981 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -183,11 +183,17 @@ const getAccessibilityPlatforms = (bsConfig) => { const addCypressZipStartLocation = (runSettings) => { let resolvedHomeDirectoryPath = path.resolve(runSettings.home_directory); let resolvedCypressConfigFilePath = path.resolve(runSettings.cypressConfigFilePath); - runSettings.cypressZipStartLocation = path.dirname(resolvedCypressConfigFilePath.split(resolvedHomeDirectoryPath)[1]); + + // Convert to POSIX style paths for consistent behavior + let posixHomePath = resolvedHomeDirectoryPath.split(path.sep).join(path.posix.sep); + let posixConfigPath = resolvedCypressConfigFilePath.split(path.sep).join(path.posix.sep); + + runSettings.cypressZipStartLocation = path.posix.dirname(posixConfigPath.split(posixHomePath)[1]); runSettings.cypressZipStartLocation = runSettings.cypressZipStartLocation.substring(1); logger.debug(`Setting cypress zip start location = ${runSettings.cypressZipStartLocation}`); } + const validate = (bsConfig, args) => { return new Promise(function (resolve, reject) { logger.info(Constants.userMessages.VALIDATING_CONFIG); From 758ee270d5cc1294d71867a6f58ca7fd6886382e Mon Sep 17 00:00:00 2001 From: Rutvik Chandla Date: Wed, 25 Jun 2025 15:28:43 +0530 Subject: [PATCH 10/10] Fix: Use TSconfig project level --- bin/helpers/readCypressConfigUtil.js | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index de4ceeda..efc60edc 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -17,35 +17,18 @@ function generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, c const working_dir = path.dirname(cypress_config_filepath); const typescript_path = path.join(bstack_node_modules_path, 'typescript', 'bin', 'tsc'); const tsc_alias_path = path.join(bstack_node_modules_path, 'tsc-alias', 'dist', 'bin', 'index.js'); - const tsConfigFilePath = bsConfig.run_settings.ts_config_file_path; + const tsConfigFilePath = path.resolve(bsConfig.run_settings.ts_config_file_path); // Prepare base temp tsconfig const tempTsConfig = { + extends: tsConfigFilePath, // Use a base tsconfig if available compilerOptions: { "outDir": `${path.basename(complied_js_dir)}`, // Add ./ prefix for consistency "listEmittedFiles": true, - "allowSyntheticDefaultImports": true, - "module": "nodenext", - "declaration": false, - "baseUrl": ".", // Default fallback baseUrl - "skipLibCheck": true }, include: [cypress_config_filepath] }; - // Inject paths and baseUrl from original tsconfig if available - if (tsConfigFilePath && fs.existsSync(tsConfigFilePath) && path.extname(tsConfigFilePath).toLowerCase() === '.json') { - const tsConfig = JSON.parse(fs.readFileSync(tsConfigFilePath, 'utf8')); - if (tsConfig.compilerOptions) { - if (tsConfig.compilerOptions.paths) { - tempTsConfig.compilerOptions.baseUrl = path.dirname(path.resolve(tsConfigFilePath)); - tempTsConfig.compilerOptions.paths = tsConfig.compilerOptions.paths; - } - } - } else { - logger.warn(`tsconfig file not found or invalid: ${tsConfigFilePath}`); - } - // Write the temporary tsconfig const tempTsConfigPath = path.join(working_dir, 'tsconfig.singlefile.tmp.json'); fs.writeFileSync(tempTsConfigPath, JSON.stringify(tempTsConfig, null, 2));