Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
8 changes: 7 additions & 1 deletion bin/helpers/capabilityHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion bin/helpers/packageInstaller.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const setupPackageFolder = (runSettings, directoryPath) => {
// Combine win and mac specific dependencies if present
if (typeof runSettings.npm_dependencies === 'object') {
Object.assign(packageJSON, {
devDependencies: combineMacWinNpmDependencies(runSettings),
devDependencies: runSettings.npm_dependencies,
});
}

Expand Down
144 changes: 133 additions & 11 deletions bin/helpers/readCypressConfigUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,120 @@
return constants.CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension) ? extension : 'js'
}

function resolveTsConfigPath(bsConfig, cypress_config_filepath) {
const working_dir = path.dirname(cypress_config_filepath);

// Priority order for finding tsconfig
const candidates = [
bsConfig.run_settings && bsConfig.run_settings.ts_config_file_path, // User specified
path.join(working_dir, 'tsconfig.json'), // Same directory as cypress config
path.join(working_dir, '..', 'tsconfig.json'), // Parent directory
path.join(process.cwd(), 'tsconfig.json') // Project root
].filter(Boolean).map(p => path.resolve(p));

for (const candidate of candidates) {
if (fs.existsSync(candidate)) {
logger.debug(`Found tsconfig at: ${candidate}`);
return candidate;
}
}

return null;
}

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');

// Smart tsconfig detection and validation
const resolvedTsConfigPath = resolveTsConfigPath(bsConfig, cypress_config_filepath);
let hasValidTsConfig = false;

if (resolvedTsConfigPath) {
try {
// Validate the tsconfig is readable and valid JSON
const tsConfigContent = fs.readFileSync(resolvedTsConfigPath, 'utf8');
JSON.parse(tsConfigContent);
hasValidTsConfig = true;
logger.info(`Using existing tsconfig: ${resolvedTsConfigPath}`);
} catch (error) {
logger.warn(`Invalid tsconfig file: ${resolvedTsConfigPath}, falling back to default configuration. Error: ${error.message}`);
hasValidTsConfig = false;
}
} else {
logger.info('No tsconfig found, using default TypeScript configuration');
}

let tempTsConfig;

if (hasValidTsConfig) {
// Scenario 1: User has valid tsconfig - use extends approach
tempTsConfig = {
extends: resolvedTsConfigPath,
compilerOptions: {
// Force override critical parameters for BrowserStack compatibility
"outDir": path.basename(complied_js_dir),
"listEmittedFiles": true,
// Ensure these are always set regardless of base tsconfig
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
include: [cypress_config_filepath]
};
} else {
// Scenario 2: No tsconfig or invalid tsconfig - create standalone with all basic parameters
tempTsConfig = {
compilerOptions: {
// Preserve old command-line parameters for backwards compatibility
"outDir": path.basename(complied_js_dir),
"listEmittedFiles": true,
"allowSyntheticDefaultImports": true,
"module": "commonjs",
"declaration": false,

// Add essential missing parameters for robust compilation
"target": "es2017",
"moduleResolution": "node",
"esModuleInterop": true,
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"strict": false, // Avoid breaking existing code
"noEmitOnError": false // Continue compilation even with errors
},
include: [cypress_config_filepath],
exclude: ["node_modules", "dist", "build"]
};
}

// 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=${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}" --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=${bstack_node_modules_path}`;
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 };
}
}

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);
Expand All @@ -22,19 +136,12 @@
}
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);
Expand All @@ -44,6 +151,21 @@
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.debug(`Temporary tsconfig file removed: ${tempTsConfigPath}`);
}

if (tsc_output) {
logger.debug(tsc_output.toString());
}

if (!tsc_output) {
logger.error('No TypeScript compilation output available');
return null;
}

const lines = tsc_output.toString().split('\n');
let foundLine = null;
for (let i = 0; i < lines.length; i++) {
Expand All @@ -53,7 +175,7 @@
}
}
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()
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@
"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",
"git-last-commit": "^1.0.1",
"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",
"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",
Expand Down
Loading
Loading