import fs from 'node:fs/promises' import path from 'node:path' import type { Argv } from 'yargs' import { getProjectRoot, replaceConfig, findInConfig, addServiceDeps, convertPackageHashToObject, formatConfigFilePaths, detectPackageManager } from '../utils.js' import { installPackages } from '../install.js' import { missingConfigurationPrompt } from './config.js' import { SUPPORTED_PACKAGES, CLI_EPILOGUE } from '../constants.js' import type { InstallCommandArguments, SupportedPackage } from '../types.js' import { canAccessConfigPath } from './utils.js' const supportedInstallations = { runner: SUPPORTED_PACKAGES.runner.map(({ value }) => convertPackageHashToObject(value)), plugin: SUPPORTED_PACKAGES.plugin.map(({ value }) => convertPackageHashToObject(value)), service: SUPPORTED_PACKAGES.service.map(({ value }) => convertPackageHashToObject(value)), reporter: SUPPORTED_PACKAGES.reporter.map(({ value }) => convertPackageHashToObject(value)), framework: SUPPORTED_PACKAGES.framework.map(({ value }) => convertPackageHashToObject(value)) } export const command = 'install ' export const desc = [ 'Add a `reporter`, `service`, or `framework` to your WebdriverIO project.', 'The command installs the package from NPM, adds it to your package.json', 'and modifies the wdio.conf.js accordingly.' ].join(' ') export const cmdArgs = { config: { desc: 'Location of your WDIO configuration (default: wdio.conf.(js|ts|cjs|mjs))', }, } as const export const builder = (yargs: Argv) => { yargs .options(cmdArgs) .epilogue(CLI_EPILOGUE) .help() for (const [type, plugins] of Object.entries(supportedInstallations)) { for (const plugin of plugins) { yargs.example(`$0 install ${type} ${plugin.short}`, `Install ${plugin.package}`) } } return yargs } export async function handler(argv: InstallCommandArguments) { /** * type = service | reporter | framework * name = names for the supported service or reporter */ const { type, name, config } = argv /** * verify for supported types via `supportedInstallations` keys */ if (!Object.keys(supportedInstallations).includes(type)) { console.log(`Type ${type} is not supported.`) if (process.env.WDIO_UNIT_TESTS) { return } process.exit(0) } /** * verify if the name of the `type` is valid */ const options = supportedInstallations[type].map((pkg) => pkg.short) if (!options.find((pkg) => pkg === name)) { console.log( `Error: ${name} is not a supported ${type}.\n\n` + `Available options for a ${type} are:\n` + `- ${options.join('\n- ')}` ) if (process.env.WDIO_UNIT_TESTS) { return } process.exit(0) } const defaultPath = path.resolve(process.cwd(), 'wdio.conf') const wdioConfPathWithNoExtension = config ? (await formatConfigFilePaths(config)).fullPathNoExtension : defaultPath const wdioConfPath = await canAccessConfigPath(wdioConfPathWithNoExtension) if (!wdioConfPath) { try { await missingConfigurationPrompt('install', wdioConfPathWithNoExtension) return handler(argv) } catch { process.exit(1) } } const configFile = await fs.readFile(wdioConfPath, { encoding: 'utf-8' }) const match = findInConfig(configFile, type) const projectRoot = await getProjectRoot() if (match && match[0].includes(name)) { console.log(`The ${type} ${name} is already part of your configuration.`) process.exit(0) } const selectedPackage = supportedInstallations[type].find(({ short }) => short === name) as SupportedPackage const pkgsToInstall = selectedPackage ? [selectedPackage.package] : [] addServiceDeps(selectedPackage ? [selectedPackage] : [], pkgsToInstall, true) const pm = detectPackageManager() console.log(`Installing "${selectedPackage.package}" using ${pm}.`) const success = await installPackages(projectRoot, pkgsToInstall, true) if (!success) { process.exit(1) } console.log(`Package "${selectedPackage.package}" installed successfully.`) const newConfig = replaceConfig(configFile, type, name) if (!newConfig) { throw new Error(`Couldn't find "${type}" property in ${path.basename(wdioConfPath)}`) } await fs.writeFile(wdioConfPath, newConfig, { encoding: 'utf-8' }) console.log('Your wdio.conf.js file has been updated.') process.exit(0) }