diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0e82f03..50d2eeb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,7 +62,7 @@ jobs: matrix: include: - os: 'macos-latest' - # - os: 'windows-2019' + - os: 'windows-latest' - os: 'ubuntu-latest' fail-fast: false @@ -86,4 +86,3 @@ jobs: - name: 'End To End (e2e) Test' run: 'npm run test:e2e-classic' - shell: 'bash' diff --git a/package-lock.json b/package-lock.json index 7a9a5fb..9238491 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,7 @@ "extract-zip": "2.0.1", "ora": "6.1.2", "read-pkg": "7.1.0", - "semver": "7.3.8", "simple-git": "3.14.1", - "sudo-prompt": "9.2.1", "table": "6.8.0", "typanion": "3.12.0", "update-notifier": "6.0.2" @@ -37,7 +35,6 @@ "@swc/core": "1.3.8", "@types/mock-fs": "4.13.1", "@types/node": "18.11.0", - "@types/semver": "7.3.12", "@types/sinon": "10.0.13", "@types/tap": "15.0.7", "@types/update-notifier": "6.0.1", @@ -682,21 +679,6 @@ "node": ">=v14" } }, - "node_modules/@commitlint/is-ignored/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@commitlint/lint": { "version": "17.1.0", "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.1.0.tgz", @@ -2241,12 +2223,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, - "node_modules/@types/semver": { - "version": "7.3.12", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.12.tgz", - "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", - "dev": true - }, "node_modules/@types/sinon": { "version": "10.0.13", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", @@ -12244,9 +12220,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -12796,11 +12772,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -16300,17 +16271,6 @@ "requires": { "@commitlint/types": "^17.0.0", "semver": "7.3.7" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@commitlint/lint": { @@ -17480,12 +17440,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, - "@types/semver": { - "version": "7.3.12", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.12.tgz", - "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", - "dev": true - }, "@types/sinon": { "version": "10.0.13", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", @@ -24651,9 +24605,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "requires": { "lru-cache": "^6.0.0" } @@ -25084,11 +25038,6 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 3f43821..802e53a 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,7 @@ "access": "public" }, "files": [ - "build", - "scripts" + "build" ], "main": "build/index.js", "bin": { @@ -70,9 +69,7 @@ "extract-zip": "2.0.1", "ora": "6.1.2", "read-pkg": "7.1.0", - "semver": "7.3.8", "simple-git": "3.14.1", - "sudo-prompt": "9.2.1", "table": "6.8.0", "typanion": "3.12.0", "update-notifier": "6.0.2" @@ -84,7 +81,6 @@ "@swc/core": "1.3.8", "@types/mock-fs": "4.13.1", "@types/node": "18.11.0", - "@types/semver": "7.3.12", "@types/sinon": "10.0.13", "@types/tap": "15.0.7", "@types/update-notifier": "6.0.1", diff --git a/scripts/dependencies/install_apk_packages.sh b/scripts/dependencies/install_apk_packages.sh deleted file mode 100755 index 43c5cf7..0000000 --- a/scripts/dependencies/install_apk_packages.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -apk update --yes - -apk add --no-cache --yes git bash curl build-base libffi-dev openssl-dev bzip2-dev zlib-dev readline-dev sqlite-dev diff --git a/scripts/dependencies/install_apt_packages.sh b/scripts/dependencies/install_apt_packages.sh deleted file mode 100755 index 270ff9d..0000000 --- a/scripts/dependencies/install_apt_packages.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -add-apt-repository --yes main -add-apt-repository --yes universe -add-apt-repository --yes restricted -add-apt-repository --yes multiverse - -apt-get update --yes - -apt-get install --yes make build-essential git bash libssl-dev zlib1g-dev \ - libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \ - libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev - -apt-get autoremove --yes diff --git a/scripts/dependencies/install_brew_packages.sh b/scripts/dependencies/install_brew_packages.sh deleted file mode 100755 index c743967..0000000 --- a/scripts/dependencies/install_brew_packages.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -brew update -brew install --force --overwrite openssl readline sqlite xz zlib git bash curl diff --git a/scripts/dependencies/install_dnf_packages.sh b/scripts/dependencies/install_dnf_packages.sh deleted file mode 100755 index 49412c8..0000000 --- a/scripts/dependencies/install_dnf_packages.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -dnf install -y make gcc zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz-devel git bash curl diff --git a/scripts/dependencies/install_pacman_packages.sh b/scripts/dependencies/install_pacman_packages.sh deleted file mode 100755 index 214c29f..0000000 --- a/scripts/dependencies/install_pacman_packages.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -pacman --sync --needed --noconfirm base-devel openssl zlib xz git bash diff --git a/scripts/dependencies/install_yum_packages.sh b/scripts/dependencies/install_yum_packages.sh deleted file mode 100755 index 4683e57..0000000 --- a/scripts/dependencies/install_yum_packages.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -yum install -y gcc zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz-devel git bash curl diff --git a/scripts/install_pipenv.sh b/scripts/install_pipenv.sh deleted file mode 100755 index 8fca2c6..0000000 --- a/scripts/install_pipenv.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -python -m pip install --user --force-reinstall pipenv - -SCRIPTS_DIRECTORY=$(dirname "$0") -pipenv_variables_file="${SCRIPTS_DIRECTORY}/pipenv_variables.sh" - -if [ -n "$ZSH_VERSION" ]; then - cat "${pipenv_variables_file}" >>"${HOME}/.zshrc" -fi - -if [ -n "$BASH_VERSION" ]; then - cat "${pipenv_variables_file}" >>"${HOME}/.bashrc" -fi diff --git a/scripts/install_pyenv.sh b/scripts/install_pyenv.sh deleted file mode 100755 index d9e2d6a..0000000 --- a/scripts/install_pyenv.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -curl https://pyenv.run | bash -export PYENV_ROOT="${HOME}/.pyenv" -export PATH="${PYENV_ROOT}/bin:${PATH}" -eval "$(pyenv init --path)" - -pyenv install 3.9.10 --force -pyenv global 3.9.10 -pyenv exec pip install --user --force-reinstall pipenv - -SCRIPTS_DIRECTORY=$(dirname "$0") -pyenv_variables_file="${SCRIPTS_DIRECTORY}/pyenv_variables.sh" -pipenv_variables_file="${SCRIPTS_DIRECTORY}/pipenv_variables.sh" - -if [ -n "$ZSH_VERSION" ]; then - cat "${pyenv_variables_file}" >>"${HOME}/.zshrc" - cat "${pipenv_variables_file}" >>"${HOME}/.zshrc" -fi - -if [ -n "$BASH_VERSION" ]; then - cat "${pyenv_variables_file}" >>"${HOME}/.bashrc" - cat "${pipenv_variables_file}" >>"${HOME}/.bashrc" -fi diff --git a/scripts/install_pyenv_macos.sh b/scripts/install_pyenv_macos.sh deleted file mode 100755 index 31d0441..0000000 --- a/scripts/install_pyenv_macos.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -brew update -brew install pyenv -export PYENV_ROOT="${HOME}/.pyenv" -export PATH="${PYENV_ROOT}/bin:${PATH}" -eval "$(pyenv init --path)" - -pyenv install 3.9.10 --force -pyenv global 3.9.10 -pyenv exec pip install --user --force-reinstall pipenv - -if [ -n "$ZSH_VERSION" ]; then - echo 'eval "$(pyenv init --path)"' >>"${HOME}/.zprofile" - - echo 'eval "$(pyenv init -)"' >>"${HOME}/.zshrc" -fi - -if [ -n "$BASH_VERSION" ]; then - echo 'export PYENV_ROOT="$HOME/.pyenv"' >>"${HOME}/.profile" - echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >>"${HOME}/.profile" - echo 'eval "$(pyenv init --path)"' >>"${HOME}/.profile" - echo 'if [ -n "$PS1" -a -n "$BASH_VERSION" ]; then source ~/.bashrc; fi' >>"${HOME}/.profile" - - echo 'eval "$(pyenv init -)"' >>"${HOME}/.bashrc" -fi diff --git a/scripts/install_python_pipenv_with_pyenv.sh b/scripts/install_python_pipenv_with_pyenv.sh deleted file mode 100755 index 1b16b8c..0000000 --- a/scripts/install_python_pipenv_with_pyenv.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -pyenv install 3.9.10 --force -pyenv global 3.9.10 -pyenv exec pip install --user --force-reinstall pipenv diff --git a/scripts/pipenv_variables.sh b/scripts/pipenv_variables.sh deleted file mode 100755 index d034084..0000000 --- a/scripts/pipenv_variables.sh +++ /dev/null @@ -1,2 +0,0 @@ -PYTHON_BIN_PATH="$(python -m site --user-base)/bin" -export PATH="$PATH:$PYTHON_BIN_PATH" diff --git a/scripts/pyenv_variables.sh b/scripts/pyenv_variables.sh deleted file mode 100755 index 3bda68b..0000000 --- a/scripts/pyenv_variables.sh +++ /dev/null @@ -1,4 +0,0 @@ -# Pyenv (Python) -export PYENV_ROOT="${HOME}/.pyenv" -export PATH="${PYENV_ROOT}/bin:${PATH}" -eval "$(pyenv init --path)" diff --git a/src/commands/create/birth.ts b/src/commands/create/birth.ts index e89e431..f64fa3e 100644 --- a/src/commands/create/birth.ts +++ b/src/commands/create/birth.ts @@ -3,7 +3,6 @@ import chalk from 'chalk' import { Leon } from '../../services/Leon.js' import { Log } from '../../services/Log.js' -import { isGNULinux, isMacOS } from '../../utils/operatingSystem.js' export class CreateBirthCommand extends Command { static paths = [['create', 'birth']] @@ -55,11 +54,6 @@ export class CreateBirthCommand extends Command { await leon.createBirth() console.log(`\n${chalk.bold.green('Success:')} Leon is born! 🎉`) console.log('You can start your leon instance:') - if (isGNULinux || isMacOS) { - console.log(`${chalk.cyan('exec $SHELL')}`) - } else { - console.log(`First, restart your command prompt.`) - } console.log(`${chalk.cyan('leon start')}`) return 0 } catch (error) { diff --git a/src/services/Leon.ts b/src/services/Leon.ts index 0f7e60e..5dd3046 100644 --- a/src/services/Leon.ts +++ b/src/services/Leon.ts @@ -2,6 +2,7 @@ import path from 'node:path' import os from 'node:os' import fs from 'node:fs' import crypto from 'node:crypto' +import stream from 'node:stream' import axios from 'axios' import ora from 'ora' @@ -123,12 +124,12 @@ export class Leon implements LeonOptions { TEMPORARY_PATH, sourceCodeInformation.folderName ) + const sourceCodeWriter = fs.createWriteStream(destination) const { data } = await axios.get(sourceCodeInformation.url, { - responseType: 'arraybuffer' - }) - await fs.promises.writeFile(destination, Buffer.from(data), { - encoding: 'binary' + responseType: 'stream' }) + data.pipe(sourceCodeWriter) + await stream.promises.finished(sourceCodeWriter) await extractZip(destination, { dir: TEMPORARY_PATH }) return extractedPath } @@ -141,7 +142,6 @@ export class Leon implements LeonOptions { } public async createBirth(): Promise { - const requirements = Requirements.getInstance() let cwdIsLeonCore = false const cwdPath = process.cwd() const cwdPackageJSONPath = path.join(cwdPath, 'package.json') @@ -177,9 +177,6 @@ export class Leon implements LeonOptions { }) } const mode = this.useDocker ? 'docker' : 'classic' - if (mode === 'classic') { - await requirements.install(this.interactive) - } if (!cwdIsLeonCore) { const sourceCodePath = await this.getSourceCode() await this.transferSourceCodeFromTemporaryToBirthPath(sourceCodePath) diff --git a/src/services/Requirements.ts b/src/services/Requirements.ts index ce22a8c..c3adb89 100644 --- a/src/services/Requirements.ts +++ b/src/services/Requirements.ts @@ -1,45 +1,9 @@ -import path from 'node:path' -import { fileURLToPath } from 'node:url' - import { execaCommand } from 'execa' -import semver from 'semver' -import ora from 'ora' - -import { LogError } from '../utils/LogError.js' -import { sudoExec } from '../utils/sudoExec.js' -import { Prompt } from './Prompt.js' -import { PyenvWindows } from './Windows/PyenvWindows.js' -import { PipenvWindows } from './Windows/PipenvWindows.js' -import { isGNULinux, isMacOS, isWindows } from '../utils/operatingSystem.js' - -export interface ExecuteScriptOptions { - loader: { - message: string - stderr: string - } - scriptCommand: string[] - sudo?: boolean -} /** * Requirements Singleton Class. */ export class Requirements { - static readonly PYTHON_VERSION = '3.9.10' - static readonly PIPENV_VERSION = '2020.11.15' - static readonly PACKAGE_MANAGERS = [ - 'apk', - 'apt', - 'brew', - 'dnf', - 'pacman', - 'yum' - ] - static readonly UNSUPPORTED_PACKAGE_MANAGER_MESSAGE = `Your package manager is not supported.\nSupported: ${Requirements.PACKAGE_MANAGERS.join( - ', ' - )}.` - static readonly UNSUPPORTED_OS_MESSAGE = `Your OS (Operating System) is not supported.\nSupported OSes: GNU/Linux, macOS and Windows.` - private static instance: Requirements private constructor() {} @@ -51,45 +15,6 @@ export class Requirements { return Requirements.instance } - public checkIfEnvironmentVariableContains( - variable: string, - content: string - ): boolean { - const environmentVariable = process.env[variable] - if (environmentVariable === undefined || environmentVariable === '') { - return false - } - return environmentVariable.includes(content) - } - - public checkVersion(version: string, requirement: string): boolean { - try { - return semver.gte(version, requirement) - } catch { - return false - } - } - - public async checkPython(): Promise { - try { - const { stdout } = await execaCommand('python --version') - const [, actualVersion] = stdout.split(' ') - return semver.eq(actualVersion, Requirements.PYTHON_VERSION) - } catch { - return false - } - } - - public async checkPipenv(): Promise { - try { - const { stdout } = await execaCommand('pipenv --version') - const [, , actualVersion] = stdout.split(' ') - return this.checkVersion(actualVersion, Requirements.PIPENV_VERSION) - } catch { - return false - } - } - public async checkSoftware(software: string): Promise { try { const { exitCode } = await execaCommand(`${software} --version`) @@ -103,129 +28,4 @@ export class Requirements { public async checkGit(): Promise { return await this.checkSoftware('git') } - - public async executeScript(options: ExecuteScriptOptions): Promise { - const { scriptCommand, loader, sudo = false } = options - const scriptLoader = ora(loader.message).start() - const scriptsUrl = new URL('../../scripts', import.meta.url) - const scriptsPath = fileURLToPath(scriptsUrl) - const commandPath = path.join(scriptsPath, ...scriptCommand) - try { - if (sudo && !isMacOS) { - try { - await sudoExec(commandPath) - } catch { - await execaCommand(`sudo --non-interactive ${commandPath}`) - } - } else { - await execaCommand(commandPath) - } - scriptLoader.succeed() - } catch (error: any) { - scriptLoader.fail() - throw new LogError({ - message: loader.stderr, - logFileMessage: error.toString() - }) - } - } - - public async installPackages(): Promise { - let packageManager: string | null = null - for (const manager of Requirements.PACKAGE_MANAGERS) { - if (await this.checkSoftware(manager)) { - packageManager = manager - break - } - } - - if (packageManager != null) { - await this.executeScript({ - scriptCommand: [ - 'dependencies', - `install_${packageManager}_packages.sh` - ], - sudo: true, - loader: { - message: 'Installing packages', - stderr: 'Failed to install needed packages' - } - }) - } else { - throw new LogError({ - message: Requirements.UNSUPPORTED_PACKAGE_MANAGER_MESSAGE - }) - } - } - - public async installPythonOnUnix(scriptToExecute: string): Promise { - const loader = { - message: 'Installing Python', - stderr: 'Failed to install Python' - } - await this.installPackages() - await this.executeScript({ - scriptCommand: [scriptToExecute], - loader - }) - } - - public async install(interactive: boolean): Promise { - const prompt = Prompt.getInstance() - const hasPython = await this.checkPython() - const hasPipenv = await this.checkPipenv() - const hasPyenv = await this.checkSoftware('pyenv') - let shouldInstallPipenvAfterPython = true - const pythonVersionString = `Python v${Requirements.PYTHON_VERSION}` - if (!hasPython) { - if ( - (interactive && (await prompt.shouldInstall(pythonVersionString))) || - !interactive - ) { - if (isGNULinux || isMacOS) { - if (hasPyenv) { - await this.installPythonOnUnix( - 'install_python_pipenv_with_pyenv.sh' - ) - } else { - await this.installPythonOnUnix( - isMacOS ? 'install_pyenv_macos.sh' : 'install_pyenv.sh' - ) - shouldInstallPipenvAfterPython = false - } - } else if (isWindows) { - const pyenvWindows = PyenvWindows.getInstance() - await pyenvWindows.install() - } else { - throw new LogError({ - message: Requirements.UNSUPPORTED_OS_MESSAGE - }) - } - } - } - if (!hasPipenv && shouldInstallPipenvAfterPython) { - if ( - (interactive && (await prompt.shouldInstall('Pipenv'))) || - !interactive - ) { - const loader = { - message: 'Installing Pipenv', - stderr: 'Failed to install Pipenv' - } - if (isGNULinux || isMacOS) { - await this.executeScript({ - scriptCommand: ['install_pipenv.sh'], - loader - }) - } else if (isWindows) { - const pipenvWindows = PipenvWindows.getInstance() - await pipenvWindows.install() - } else { - throw new LogError({ - message: Requirements.UNSUPPORTED_OS_MESSAGE - }) - } - } - } - } } diff --git a/src/services/Windows/PipenvWindows.ts b/src/services/Windows/PipenvWindows.ts deleted file mode 100644 index 760a6c4..0000000 --- a/src/services/Windows/PipenvWindows.ts +++ /dev/null @@ -1,65 +0,0 @@ -import path from 'node:path' - -import { execaCommand } from 'execa' -import ora from 'ora' - -import { LogError } from '../../utils/LogError.js' -import { - extractVersionForPath, - getPythonSiteString, - getPythonVersionString -} from '../../utils/pythonUtils.js' -import { addToPathOnWindows } from '../../utils/pathUtils.js' - -/** - * PipenvWindows Singleton Class. - */ -export class PipenvWindows { - private static instance: PipenvWindows - - private constructor() {} - - public static getInstance(): PipenvWindows { - if (PipenvWindows.instance == null) { - PipenvWindows.instance = new PipenvWindows() - } - return PipenvWindows.instance - } - - public async install(): Promise { - const pipenvLoader = ora('Installing pipenv').start() - try { - await execaCommand('pip install --user pipenv', { - shell: 'powershell.exe' - }) - await this.addToPath() - pipenvLoader.succeed() - } catch (error: any) { - pipenvLoader.fail() - throw new LogError({ - message: 'Could not install pipenv', - logFileMessage: error.toString() - }) - } - } - - public async addToPath(): Promise { - try { - const pythonSitePath = await getPythonSiteString() - const formattedPythonVersion = extractVersionForPath( - await getPythonVersionString() - ) - const fullPathToPythonSite = path.join( - pythonSitePath, - `Python${formattedPythonVersion}`, - 'Scripts' - ) - await addToPathOnWindows(fullPathToPythonSite) - } catch (error: any) { - throw new LogError({ - message: 'Impossible to register Pipenv environment variables', - logFileMessage: error.toString() - }) - } - } -} diff --git a/src/services/Windows/PyenvWindows.ts b/src/services/Windows/PyenvWindows.ts deleted file mode 100644 index c8e8e5e..0000000 --- a/src/services/Windows/PyenvWindows.ts +++ /dev/null @@ -1,134 +0,0 @@ -import os from 'node:os' -import fs from 'node:fs' -import path from 'node:path' - -import extractZip from 'extract-zip' -import axios from 'axios' -import { execa } from 'execa' -import ora from 'ora' - -import { - createTemporaryEmptyFolder, - TEMPORARY_PATH -} from '../../utils/createTemporaryEmptyFolder.js' -import { LogError } from '../../utils/LogError.js' -import { copyDirectory } from '../../utils/copyDirectory.js' -import { - addToPathOnWindows, - addEnvironmentVariableOnWindows, - getWindowsUserPath -} from '../../utils/pathUtils.js' -import { isExistingPath } from '../../utils/isExistingPath.js' -import { Requirements } from '../Requirements.js' - -/** - * PyenvWindows Singleton Class. - */ -export class PyenvWindows { - static NAME = 'pyenv-win' - static GITHUB_URL = `https://github.com/${PyenvWindows.NAME}/${PyenvWindows.NAME}` - static PYENV_PATH = path.join(os.homedir(), '.pyenv') - static PYENV_WIN_PATH = `${PyenvWindows.PYENV_PATH}\\${PyenvWindows.NAME}\\` - static PYENV_WIN_PATH_VALUE = `${PyenvWindows.PYENV_WIN_PATH}\\bin;${PyenvWindows.PYENV_WIN_PATH}\\shims` - static PYTHON_VERSION = '3.9.10' - - private static instance: PyenvWindows - - private constructor() {} - - public static getInstance(): PyenvWindows { - if (PyenvWindows.instance == null) { - PyenvWindows.instance = new PyenvWindows() - } - return PyenvWindows.instance - } - - private async installPython(): Promise { - const pythonLoader = ora( - `Installing python ${PyenvWindows.PYTHON_VERSION}` - ).start() - try { - await execa(`pyenv install ${PyenvWindows.PYTHON_VERSION}`) - await execa(`pyenv rehash`) - await execa(`pyenv global ${PyenvWindows.PYTHON_VERSION}`) - const path = await getWindowsUserPath() - process.env.PATH = `${path};${process.env.PATH ?? ''};` - pythonLoader.succeed() - } catch (error: any) { - pythonLoader.fail() - throw new LogError({ - message: `Could not install python ${PyenvWindows.PYTHON_VERSION}`, - logFileMessage: error.toString() - }) - } - } - - private async registerInPath(): Promise { - const varEnvLoader = ora('Registering environment variables').start() - try { - await addEnvironmentVariableOnWindows( - 'PYENV', - PyenvWindows.PYENV_WIN_PATH - ) - await addEnvironmentVariableOnWindows( - 'PYENV_ROOT', - PyenvWindows.PYENV_WIN_PATH - ) - await addEnvironmentVariableOnWindows( - 'PYENV_HOME', - PyenvWindows.PYENV_WIN_PATH - ) - await addToPathOnWindows(PyenvWindows.PYENV_WIN_PATH_VALUE) - varEnvLoader.succeed() - } catch (error: any) { - varEnvLoader.fail() - throw new LogError({ - message: 'Impossible to register Pyenv environment variables', - logFileMessage: error.toString() - }) - } - } - - private async installPyenv(): Promise { - const loader = ora('Installing Pyenv for Windows').start() - try { - const version = 'master' - const folderName = `${PyenvWindows.NAME}-${version}` - const zipName = `${folderName}.zip` - const url = `${PyenvWindows.GITHUB_URL}/archive/${version}.zip` - const pyenvZipPath = path.join(TEMPORARY_PATH, zipName) - const pyenvExtractedPath = path.join(TEMPORARY_PATH, folderName) - await createTemporaryEmptyFolder() - const { data } = await axios.get(url, { - responseType: 'arraybuffer' - }) - await fs.promises.writeFile(pyenvZipPath, Buffer.from(data), { - encoding: 'binary' - }) - await extractZip(pyenvZipPath, { dir: TEMPORARY_PATH }) - await fs.promises.mkdir(PyenvWindows.PYENV_PATH, { - recursive: true - }) - await copyDirectory(pyenvExtractedPath, PyenvWindows.PYENV_PATH) - loader.succeed() - } catch (error: any) { - loader.fail() - throw new LogError({ - message: `Could not install Pyenv`, - logFileMessage: error.toString() - }) - } - } - - public async install(): Promise { - const requirements = Requirements.getInstance() - const hasPyenv = - (await isExistingPath(PyenvWindows.PYENV_PATH)) || - (await requirements.checkSoftware('pyenv')) - if (!hasPyenv) { - await this.installPyenv() - await this.registerInPath() - } - await this.installPython() - } -} diff --git a/src/services/__test__/Requirements.test.ts b/src/services/__test__/Requirements.test.ts deleted file mode 100644 index 4ad2c04..0000000 --- a/src/services/__test__/Requirements.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import os from 'node:os' -import path from 'node:path' - -import tap from 'tap' -import mockedEnv from 'mocked-env' - -import { Requirements } from '../Requirements.js' - -const requirements = Requirements.getInstance() - -await tap.test('services/Requirements', async (t) => { - await t.test('checkVersion', async (t) => { - await t.test('should return false when there is no match', async (t) => { - const requirement = '3.7.2' - const commandAnswer = 'this command does not exist' - t.equal(requirements.checkVersion(commandAnswer, requirement), false) - const commandAnswer2 = 'python version is 3.2.0' - t.equal(requirements.checkVersion(commandAnswer2, requirement), false) - }) - - await t.test( - 'should return false when requirement is wrongly typed', - async (t) => { - const requirement = 'wrong requirement' - const commandAnswer = '3.7.2' - t.equal(requirements.checkVersion(commandAnswer, requirement), false) - } - ) - - await t.test( - 'should return true when the version is higher or equal than the requirement', - async (t) => { - const requirement = '3.0.0' - const commandAnswer = '3.7.2' - t.equal(requirements.checkVersion(commandAnswer, requirement), true) - const requirement2 = '3.7.2' - t.equal(requirements.checkVersion(commandAnswer, requirement2), true) - } - ) - - await t.test( - 'should return false when the version is less than the requirement', - async (t) => { - const requirement = '3.9.10' - const commandAnswer = '3.9.7' - t.equal(requirements.checkVersion(commandAnswer, requirement), false) - } - ) - }) - - await t.test('checkPython', async (t) => { - await t.test('should return a boolean', async () => { - const result = await requirements.checkPython() - t.type(result, 'boolean') - }) - }) - - await t.test('checkEnvironmentVariable', async (t) => { - await t.test( - 'should return false because the environment variable is not set', - async (t) => { - const restore = mockedEnv({ - PYENV: undefined - }) - t.equal( - requirements.checkIfEnvironmentVariableContains( - 'PYENV', - path.join('.pyenv', 'pyenv-win') - ), - false - ) - restore() - } - ) - - await t.test( - "should return false because the environment variable doesn't contains the specifield value", - async (t) => { - const restore = mockedEnv({ - PYENV: path.join('some', 'value', 'here') - }) - t.equal( - requirements.checkIfEnvironmentVariableContains( - 'PYENV', - path.join('.pyenv', 'pyenv-win') - ), - false - ) - restore() - } - ) - - await t.test( - 'should return true because the environment variable contains the specifield value', - async (t) => { - const pyenvValue = path.join(os.homedir(), '.pyenv', 'pyenv-win') - const restore = mockedEnv({ - PYENV: pyenvValue - }) - t.equal( - requirements.checkIfEnvironmentVariableContains( - 'PYENV', - path.join('.pyenv', 'pyenv-win') - ), - true - ) - restore() - } - ) - }) -}) diff --git a/src/utils/__test__/pythonUtils.test.ts b/src/utils/__test__/pythonUtils.test.ts deleted file mode 100644 index d204407..0000000 --- a/src/utils/__test__/pythonUtils.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import tap from 'tap' - -import { extractVersionForPath } from '../pythonUtils.js' - -await tap.test('utils/pythonUtils', async (t) => { - await t.test('should return 310 if given "Python 3.10.0b3"', async (t) => { - const str = 'Python 3.10.0b3' - const result = extractVersionForPath(str) - t.equal(result, '310') - }) - - await t.test( - "should return an error if it doesn't exactly have one match", - async (t) => { - const str1 = 'Python 3.10.0 lorem ipsum 3.7.2' - t.throws(() => { - return extractVersionForPath(str1) - }) - } - ) -}) diff --git a/src/utils/createTemporaryEmptyFolder.ts b/src/utils/createTemporaryEmptyFolder.ts index bd02597..c1b125a 100644 --- a/src/utils/createTemporaryEmptyFolder.ts +++ b/src/utils/createTemporaryEmptyFolder.ts @@ -1,14 +1,10 @@ import { fileURLToPath } from 'node:url' import fs from 'node:fs' -import { isExistingPath } from './isExistingPath.js' - export const TEMPORARY_URL = new URL('../../temp', import.meta.url) export const TEMPORARY_PATH = fileURLToPath(TEMPORARY_URL) export const createTemporaryEmptyFolder = async (): Promise => { - if (await isExistingPath(TEMPORARY_PATH)) { - await fs.promises.rm(TEMPORARY_URL, { recursive: true, force: true }) - } + await fs.promises.rm(TEMPORARY_URL, { recursive: true, force: true }) await fs.promises.mkdir(TEMPORARY_URL) } diff --git a/src/utils/operatingSystem.ts b/src/utils/operatingSystem.ts deleted file mode 100644 index 3698846..0000000 --- a/src/utils/operatingSystem.ts +++ /dev/null @@ -1,7 +0,0 @@ -import os from 'node:os' - -const operatingSystemType = os.type() - -export const isGNULinux = operatingSystemType === 'Linux' -export const isMacOS = operatingSystemType === 'Darwin' -export const isWindows = operatingSystemType === 'Windows_NT' diff --git a/src/utils/pathUtils.ts b/src/utils/pathUtils.ts deleted file mode 100644 index 48215d6..0000000 --- a/src/utils/pathUtils.ts +++ /dev/null @@ -1,66 +0,0 @@ -import os from 'node:os' - -import { execa } from 'execa' - -import { LogError } from './LogError.js' - -export const getWindowsUserPath = async (): Promise => { - const errorMessage = 'Could not retrieve user path from windows' - try { - const { stdout: userPath, failed } = await execa( - `[Environment]::GetEnvironmentVariable('PATH', 'User')`, - [], - { - shell: 'powershell.exe' - } - ) - if (failed) { - throw new LogError({ message: errorMessage }) - } - return userPath - } catch (error: any) { - throw new LogError({ - message: errorMessage, - logFileMessage: error - }) - } -} - -export const addToPathOnWindows = async (pathToAdd: string): Promise => { - let path = await getWindowsUserPath() - if (path === '') { - return - } - - // Windows 10 1905 or newer => We need to disable the built-in Python launcher (Microsoft Store) Execution Alias - // Deleting "C:\Users\Username\AppData\Local\Microsoft\WindowsApps\" in the PATH, prevent the Microsoft Store application from launching (see: ) - const pathToReplace = `${os.homedir()}\\AppData\\Local\\Microsoft\\WindowsApps;` - path = path.replace(pathToReplace, '') - - const separator = - path.length > 0 && path.charAt(path.length - 1) !== ';' ? ';' : '' - await execa( - `[Environment]::SetEnvironmentVariable('PATH', "${path}${separator}${pathToAdd};", 'User')`, - [], - { shell: 'powershell.exe' } - ) - process.env.PATH = `${process.env.PATH ?? ''}${separator}${pathToAdd};` -} - -export const addEnvironmentVariableOnWindows = async ( - variable: string, - value: string -): Promise => { - try { - await execa( - `[Environment]::SetEnvironmentVariable('${variable}', '${value}', 'User')`, - [], - { shell: 'powershell.exe' } - ) - } catch (error: any) { - throw new LogError({ - message: `Could not save the following environment variable "${variable}" with the value "${value}"`, - logFileMessage: error - }) - } -} diff --git a/src/utils/pythonUtils.ts b/src/utils/pythonUtils.ts deleted file mode 100644 index 02d7176..0000000 --- a/src/utils/pythonUtils.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { execaCommand } from 'execa' - -import { LogError } from './LogError.js' - -export const extractVersionForPath = (str: string): string => { - const match = str.match(/\d\.\d{1,3}/g) - if (match?.length !== 1) { - throw new LogError({ - message: 'Could not extract python version : no relevant match' - }) - } - return match[0].split('.').join('') -} - -export const extractPythonVersionForSemver = (str: string): string => { - const match = str.match(/\d(?:\.\d{1,3}){2}/g) - if (match?.length !== 1) { - throw new LogError({ - message: 'Could not extract python version : no relevant match' - }) - } - return match[0].toString() -} - -export const getPythonSiteString = async (): Promise => { - const errorMessage = 'Error while getting the path of python libraries' - try { - const { stdout: pythonSite, failed: pythonSiteFailed } = await execaCommand( - 'python -m site --user-base', - { shell: 'powershell.exe' } - ) - if (pythonSiteFailed) { - throw new LogError({ message: errorMessage }) - } - return pythonSite - } catch (error: any) { - throw new LogError({ message: errorMessage, logFileMessage: error }) - } -} - -export const getPythonVersionString = async (): Promise => { - const errorMessage = 'Error while getting the version of python' - try { - const { stdout: pythonVersionString, failed: pythonVersionFailed } = - await execaCommand('python --version', { shell: 'powershell.exe' }) - if (pythonVersionFailed) { - throw new LogError({ message: errorMessage }) - } - return pythonVersionString - } catch (error: any) { - throw new LogError({ message: errorMessage, logFileMessage: error }) - } -} diff --git a/src/utils/sudoExec.ts b/src/utils/sudoExec.ts deleted file mode 100644 index 5bddcf0..0000000 --- a/src/utils/sudoExec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import sudoPrompt from 'sudo-prompt' - -import { Leon } from '../services/Leon.js' - -export const sudoExec = async (command: string): Promise => { - return await new Promise((resolve, reject) => { - sudoPrompt.exec( - command, - { - name: Leon.NAME - }, - (error, stdout) => { - if (error != null) { - reject(error) - } else { - resolve(stdout?.toString() ?? '') - } - } - ) - }) -}