🏡 index : quine-global/webdriverio.git

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 <type> <name>'
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)
}