diff --git a/index.js b/index.js index 0ab813e9c..16cd62567 100755 --- a/index.js +++ b/index.js @@ -42,7 +42,10 @@ import { sendFeedback } from "./scripts/feedback.js"; import { logout } from "./scripts/logout.js"; import { modulesArchive, modulesGet, modulesList } from "./scripts/modules.js"; import { publish } from "./scripts/publish.js"; -import { preExecuteChecks } from "./scripts/utils/environment.js"; +import { + validateEnvironmentDependencies, + EnvironmentDependency +} from "./scripts/utils/environment.js"; import { analytics } from "./scripts/analytics/wrapper.js"; import { HAS_ASKED_OPT_IN_NAME } from "./scripts/analytics/config.js"; import { EVENT } from "./scripts/analytics/constants.js"; @@ -109,7 +112,10 @@ async function dispatcher() { const commands = { demo: () => { - preExecuteChecks(); + validateEnvironmentDependencies([ + EnvironmentDependency.Python, + EnvironmentDependency.PipEnv + ]); createDemo( path.join(gitRoot(), "demo"), path.join(sourceDir, "cookiecutter.yaml") @@ -139,7 +145,11 @@ const commands = { } }, add: () => { - preExecuteChecks(); + validateEnvironmentDependencies([ + EnvironmentDependency.Python, + EnvironmentDependency.Yarn + ]); + const args = arg({ "--source": String, "--project": String @@ -151,6 +161,8 @@ const commands = { addModules(modules, args["--source"], args["--project"], gitRoot()); }, remove: () => { + validateEnvironmentDependencies([EnvironmentDependency.Yarn]); + const args = arg({ "--source": String, "--project": String @@ -162,7 +174,11 @@ const commands = { removeModules(modules, args["--source"], args["--project"], gitRoot()); }, create: () => { - preExecuteChecks(true); + validateEnvironmentDependencies([ + EnvironmentDependency.Python, + EnvironmentDependency.CookieCutter + ]); + const args = arg({ "--name": String, "--type": String, @@ -199,6 +215,8 @@ const commands = { commitModules(modules, args["--source"], gitRoot()); }, init: () => { + validateEnvironmentDependencies([EnvironmentDependency.Git]); + const args = arg({ "--name": String }); diff --git a/scripts/info.js b/scripts/info.js index 2fbca8c2f..a3aa059bb 100644 --- a/scripts/info.js +++ b/scripts/info.js @@ -1,7 +1,10 @@ import { invalid, section } from "../utils.js"; import { apiClient } from "./utils/apiClient.js"; +import { validateEnvironmentDependencies } from "./utils/environment.js"; export const info = async () => { + validateEnvironmentDependencies(undefined, true); + const response = await apiClient.get({ path: "/v2/user/" }); diff --git a/scripts/utils/environment.js b/scripts/utils/environment.js index 1acf7b32b..5ebf9c58f 100644 --- a/scripts/utils/environment.js +++ b/scripts/utils/environment.js @@ -1,10 +1,22 @@ import { execSync, spawnSync } from "node:child_process"; -import { invalid, section } from "../../utils.js"; +import { invalid, section, valid } from "../../utils.js"; +import { configFile } from "./configFile.js"; + +const ENVIRONMENT_VERSIONS_CONFIG_NAME = "environment-versions"; +const PYTHON_VERSION_REGEX = /Python (3\.[0-9]*)/; const userdir = process.cwd(); export const execOptions = { encoding: "utf8", stdio: "inherit" }; +export const EnvironmentDependency = { + Yarn: "yarn", + Git: "git", + Python: "python", + PipEnv: "pipenv", + CookieCutter: "cookiecutter" +}; + /** * Setup Python environment to desired version * @param {*} options: exec options @@ -13,37 +25,158 @@ export function configurePython(options = execOptions) { execSync("pipenv --python 3.8.17", options); } -export function preExecuteChecks(cookiecutterCheck = false) { - // Check if Python 3.8.x is installed +function formatStdout(stdout) { + return stdout.replace(/\n/g, ""); +} + +export function getEnvironmentVersions(dependencies) { + const environmentVersions = {}; + + if (dependencies.includes(EnvironmentDependency.Yarn)) { + const yarn = spawnSync("yarn", ["--version"], { + cwd: userdir, + shell: true, + encoding: "utf8" + }); + + if (!yarn.error && !yarn.stderr) { + environmentVersions.yarn = formatStdout(yarn.stdout); + } + } + + if (dependencies.includes(EnvironmentDependency.Git)) { + const git = spawnSync("git", ["--version"], { + cwd: userdir, + shell: true, + encoding: "utf8" + }); + + if (!git.error && !git.stderr) { + environmentVersions.git = formatStdout(git.stdout); + } + } + + if (dependencies.includes(EnvironmentDependency.Python)) { + const python = spawnSync("python", ["--version"], { + cwd: userdir, + shell: true, + encoding: "utf8" + }); - try { - section("Checking Python version"); + if (python.stdout && !python.error && !python.stderr) { + const versionMatch = python.stdout.match(PYTHON_VERSION_REGEX); - const pythonVersionRegex = /Python 3\.[0-9]*/; + if (versionMatch && versionMatch[1]) { + environmentVersions.python = versionMatch[1]; + } + } + } - const pythonCheck = spawnSync("python", ["--version"], { + if (dependencies.includes(EnvironmentDependency.PipEnv)) { + const pipenv = spawnSync("pipenv", ["--version"], { cwd: userdir, shell: true, encoding: "utf8" }); - const userPythonVersion = pythonCheck.stdout || pythonCheck.stderr; - if (!pythonVersionRegex.test(userPythonVersion)) { - invalid(`Found Python version: ${userPythonVersion}. Please install 3.x and try again.`); - } - } catch (error) { - invalid("Error detecting python version, please check install and try again."); - } - - if (cookiecutterCheck) { - // Check if Cookiecutter is installed - try { - execSync("cookiecutter --version", { - cwd: userdir, - shell: true, - encoding: "utf8" - }); - } catch (error) { - invalid("Cookiecutter is not installed. Please install Cookiecutter before running this command."); + + if (!pipenv.stderr && !pipenv.error) { + environmentVersions.pipenv = formatStdout(pipenv.stdout); + } + } + + if (EnvironmentDependency.CookieCutter) { + const cookiecutter = spawnSync("cookiecutter --version", { + cwd: userdir, + shell: true, + encoding: "utf8" + }); + + if (!cookiecutter.stderr && !cookiecutter.error) { + environmentVersions.cookiecutter = cookiecutter.stdout; + } + } + + return environmentVersions; +} + +export function validateEnvironmentDependencies( + dependencies = [ + EnvironmentDependency.Yarn, + EnvironmentDependency.Git, + EnvironmentDependency.Python, + EnvironmentDependency.PipEnv, + EnvironmentDependency.CookieCutter + ], + force = false +) { + section("Checking environment compatibility"); + + const configValues = configFile.get(ENVIRONMENT_VERSIONS_CONFIG_NAME); + + const cachedEnvironmentVersions = !force && configValues ? configValues : {}; + + let missingEnvironmentVersions = dependencies; + + if (!force) { + missingEnvironmentVersions = dependencies.filter( + (dependency) => !cachedEnvironmentVersions[dependency] + ); + } + + const currentEnvironmentVersions = getEnvironmentVersions( + missingEnvironmentVersions + ); + + const environmentVersions = { + ...cachedEnvironmentVersions, + ...currentEnvironmentVersions + }; + + configFile.set(ENVIRONMENT_VERSIONS_CONFIG_NAME, environmentVersions); + configFile.save(); + + const printInvalidMessage = (message) => + invalid( + `${message}\n\nVisit the following page for environment requirements https://github.com/crowdbotics/modules?tab=readme-ov-file#requirements-for-contributing` + ); + + if (dependencies.includes(EnvironmentDependency.Yarn)) { + if (!environmentVersions.yarn) { + printInvalidMessage("yarn is not available in your system"); + } else { + valid("yarn version", environmentVersions.yarn); + } + } + + if (dependencies.includes(EnvironmentDependency.Git)) { + if (!environmentVersions.git) { + printInvalidMessage("git is not available in your system"); + } else { + valid(environmentVersions.git); + } + } + + if (dependencies.includes(EnvironmentDependency.Python)) { + if (!environmentVersions.python) { + printInvalidMessage("Python 3.x is not available in your system"); + } else { + valid(environmentVersions.python); + } + } + + if (dependencies.includes(EnvironmentDependency.PipEnv)) { + if (!environmentVersions.pipenv) { + printInvalidMessage("pipenv is not available in your system"); + } else { + valid(environmentVersions.pipenv); + } + } + + if (dependencies.includes(EnvironmentDependency.CookieCutter)) { + if (!environmentVersions.cookiecutter) { + printInvalidMessage("cookiecutter is not available in your system"); + } else { + valid(environmentVersions.cookiecutter); } } }