diff --git a/build-packages/magento-scripts/lib/tasks/php/bundled-extensions.js b/build-packages/magento-scripts/lib/tasks/php/bundled-extensions.js new file mode 100644 index 00000000..a870428c --- /dev/null +++ b/build-packages/magento-scripts/lib/tasks/php/bundled-extensions.js @@ -0,0 +1,28 @@ +const bundledExtensions = [ + 'bz2', + 'bcmath', + 'ctype', + 'curl', + 'intl', + 'dom', + 'filter', + 'hash', + 'sockets', + 'iconv', + 'json', + 'mbstring', + 'openssl', + 'xml', + 'mysql', + 'pdo', + 'soap', + 'xmlrpc', + 'xml', + 'zip', + 'fpm', + 'gd' +]; + +module.exports = { + bundledExtensions +}; diff --git a/build-packages/magento-scripts/lib/tasks/php/compile-options.js b/build-packages/magento-scripts/lib/tasks/php/compile-options.js index b8d35bab..c81e9cb2 100644 --- a/build-packages/magento-scripts/lib/tasks/php/compile-options.js +++ b/build-packages/magento-scripts/lib/tasks/php/compile-options.js @@ -1,30 +1,16 @@ +const { bundledExtensions } = require('./bundled-extensions'); + +const darwinVariants = [ + 'openssl=$(brew --prefix openssl@1.1)', // ="$(brew --prefix openssl@1.1)" + 'curl=$(brew --prefix curl)', + 'intl=$(brew --prefix icu4c)', + 'bz2="$(brew --prefix bzip2)"' +]; + const compileOptions = { linux: { cpuCount: '$(nproc)', - variants: [ - '+bz2', - '+bcmath', - '+ctype', - '+curl', - '+intl', - '+dom', - '+filter', - '+hash', - '+sockets', - '+iconv', - '+json', - '+mbstring', - '+openssl', - '+xml', - '+mysql', - '+pdo', - '+soap', - '+xmlrpc', - '+xml', - '+zip', - '+fpm', - '+gd' - ], + variants: bundledExtensions.map((ext) => `+${ext}`), extraOptions: [ '--with-freetype-dir=/usr/include/freetype2', '--with-openssl=/usr/', @@ -39,30 +25,14 @@ const compileOptions = { }, darwin: { cpuCount: '$(sysctl -n hw.ncpu)', - variants: [ - '+neutral', - '+bz2="$(brew --prefix bzip2)"', - '+bcmath', - '+ctype', - '+curl=$(brew --prefix curl)', - '+intl=$(brew --prefix icu4c)', - '+dom', - '+filter', - '+hash', - '+iconv', - '+json', - '+mbstring', - '+openssl=$(brew --prefix openssl@1.1)', // ="$(brew --prefix openssl@1.1)" - '+xml', - '+mysql', - '+pdo', - '+soap', - '+xmlrpc', - '+xml', - '+zip', - '+fpm', - '+gd' - ], + variants: bundledExtensions.map((ext) => { + const darwinVariant = darwinVariants.find((dv) => dv.startsWith(ext)); + if (darwinVariant) { + return `+${darwinVariant}`; + } + + return `+${ext}`; + }), extraOptions: [ '--with-zlib-dir=$(brew --prefix zlib)', '--with-iconv=$(brew --prefix libiconv)', diff --git a/build-packages/magento-scripts/lib/tasks/php/compile.js b/build-packages/magento-scripts/lib/tasks/php/compile.js index 59c78246..0d39ef74 100644 --- a/build-packages/magento-scripts/lib/tasks/php/compile.js +++ b/build-packages/magento-scripts/lib/tasks/php/compile.js @@ -6,7 +6,7 @@ const compileOptions = require('./compile-options'); /** * @type {() => import('listr2').ListrTask} */ -const compile = () => ({ +const compilePHP = () => ({ title: 'Compiling PHP', task: async ({ config: { php } }, task) => { const platformCompileOptions = compileOptions[process.platform]; @@ -44,4 +44,4 @@ const compile = () => ({ } }); -module.exports = compile; +module.exports = compilePHP; diff --git a/build-packages/magento-scripts/lib/tasks/php/configure.js b/build-packages/magento-scripts/lib/tasks/php/configure.js index ca1a7791..8bfeb915 100644 --- a/build-packages/magento-scripts/lib/tasks/php/configure.js +++ b/build-packages/magento-scripts/lib/tasks/php/configure.js @@ -1,76 +1,17 @@ /* eslint-disable max-len */ -const path = require('path'); -const fs = require('fs'); -const { execAsyncSpawn } = require('../../util/exec-async-command'); -const pathExists = require('../../util/path-exists'); const enableExtension = require('./extensions/enable'); const installExtension = require('./extensions/install'); const disableExtension = require('./extensions/disable'); -const phpbrewConfig = require('../../config/phpbrew'); - -/** - * Get enabled extensions list with versions - * @param {import('../../../typings/context').ListrContext['config']} param0 - * @returns {Promise<{[key: string]: string}}>} - */ -const getEnabledExtensions = async ({ php }) => { - const output = await execAsyncSpawn( - `${ php.binPath } -c ${php.iniPath} -r 'foreach (get_loaded_extensions() as $extension) echo "$extension:" . phpversion($extension) . "\n";'` - ); - - return output - .split('\n') - .map((m) => { - // eslint-disable-next-line no-unused-vars - const [_, moduleName, moduleVersion] = m.match(/(.+):(.+)/i); - - return [moduleName, moduleVersion]; - }) - .reduce((acc, [name, version]) => ({ ...acc, [name]: version }), {}); -}; - -/** - * Get disabled extensions list - * @param {import('../../../typings/context').ListrContext['config']} param0 - * @returns {Promise} - */ -const getDisabledExtensions = async ({ php }) => { - const extensionsIniDirectory = path.join(phpbrewConfig.phpPath, `php-${php.version}`, 'var', 'db'); - - if (!await pathExists(extensionsIniDirectory)) { - return []; - } - - const extensionIniList = await fs.promises.readdir( - extensionsIniDirectory, - { - encoding: 'utf-8', - withFileTypes: true - } - ); - - return extensionIniList.filter((f) => f.isFile() && f.name.endsWith('.disabled')).map((f) => f.name.replace('.disabled', '')); -}; - -/** - * Get installed extensions - * @param {import('../../../typings/context').ListrContext['config']} param0 - * @returns {Promise} - */ -const getInstalledExtensions = async ({ php }) => { - const extensionDirectory = path.join(phpbrewConfig.buildPath, `php-${php.version}`, 'ext'); - - const availableExtensions = await fs.promises.readdir(extensionDirectory, { - encoding: 'utf-8' - }); - - return availableExtensions; -}; +const { + getEnabledExtensions, + getInstalledExtensions, + getDisabledExtensions +} = require('./extensions'); /** * @returns {import('listr2').ListrTask} */ -const configure = () => ({ +const configurePHP = () => ({ title: 'Configuring PHP extensions', task: async ({ config, debug }, task) => { const { php, php: { disabledExtensions = [] } } = config; @@ -145,4 +86,4 @@ const configure = () => ({ } }); -module.exports = configure; +module.exports = configurePHP; diff --git a/build-packages/magento-scripts/lib/tasks/php/extensions/index.js b/build-packages/magento-scripts/lib/tasks/php/extensions/index.js new file mode 100644 index 00000000..fb072f59 --- /dev/null +++ b/build-packages/magento-scripts/lib/tasks/php/extensions/index.js @@ -0,0 +1,71 @@ +/* eslint-disable max-len */ +const path = require('path'); +const fs = require('fs'); +const { execAsyncSpawn } = require('../../../util/exec-async-command'); +const pathExists = require('../../../util/path-exists'); +const phpbrewConfig = require('../../../config/phpbrew'); + +/** + * Get enabled extensions list with versions + * @param {import('../../../../typings/context').ListrContext['config']} param0 + * @returns {Promise<{[key: string]: string}}>} + */ +const getEnabledExtensions = async ({ php }) => { + const output = await execAsyncSpawn( + `${ php.binPath } -c ${php.iniPath} -r 'foreach (get_loaded_extensions() as $extension) echo "$extension:" . phpversion($extension) . "\n";'` + ); + + return output + .split('\n') + .map((m) => { + // eslint-disable-next-line no-unused-vars + const [_, moduleName, moduleVersion] = m.match(/(.+):(.+)/i); + + return [moduleName, moduleVersion]; + }) + .reduce((acc, [name, version]) => ({ ...acc, [name]: version }), {}); +}; + +/** + * Get disabled extensions list + * @param {import('../../../../typings/context').ListrContext['config']} param0 + * @returns {Promise} + */ +const getDisabledExtensions = async ({ php }) => { + const extensionsIniDirectory = path.join(phpbrewConfig.phpPath, `php-${php.version}`, 'var', 'db'); + + if (!await pathExists(extensionsIniDirectory)) { + return []; + } + + const extensionIniList = await fs.promises.readdir( + extensionsIniDirectory, + { + encoding: 'utf-8', + withFileTypes: true + } + ); + + return extensionIniList.filter((f) => f.isFile() && f.name.endsWith('.disabled')).map((f) => f.name.replace('.disabled', '')); +}; + +/** + * Get installed extensions + * @param {import('../../../../typings/context').ListrContext['config']} param0 + * @returns {Promise} + */ +const getInstalledExtensions = async ({ php }) => { + const extensionDirectory = path.join(phpbrewConfig.buildPath, `php-${php.version}`, 'ext'); + + const availableExtensions = await fs.promises.readdir(extensionDirectory, { + encoding: 'utf-8' + }); + + return availableExtensions; +}; + +module.exports = { + getEnabledExtensions, + getDisabledExtensions, + getInstalledExtensions +}; diff --git a/build-packages/magento-scripts/lib/tasks/php/index.js b/build-packages/magento-scripts/lib/tasks/php/index.js index 4d7f45d1..fa50cc39 100644 --- a/build-packages/magento-scripts/lib/tasks/php/index.js +++ b/build-packages/magento-scripts/lib/tasks/php/index.js @@ -2,8 +2,8 @@ const fs = require('fs'); const path = require('path'); const logger = require('@scandipwa/scandipwa-dev-utils/logger'); const pathExists = require('../../util/path-exists'); -const compile = require('./compile'); -const configure = require('./configure'); +const compilePhp = require('./compile'); +const configurePhp = require('./configure'); const updatePhpBrew = require('./update-phpbrew'); const phpbrewConfig = require('../../config/phpbrew'); @@ -63,7 +63,7 @@ const installPhp = () => ({ // eslint-disable-next-line consistent-return return task.newListr([ updatePhpBrew(), - compile() + compilePhp() ], { concurrent: false, exitOnError: true @@ -73,6 +73,6 @@ const installPhp = () => ({ module.exports = { installPhp, - compilePhp: compile, - configurePhp: configure + compilePhp, + configurePhp }; diff --git a/build-packages/magento-scripts/lib/tasks/php/validate-php.js b/build-packages/magento-scripts/lib/tasks/php/validate-php.js new file mode 100644 index 00000000..ccdbe618 --- /dev/null +++ b/build-packages/magento-scripts/lib/tasks/php/validate-php.js @@ -0,0 +1,67 @@ +const logger = require('@scandipwa/scandipwa-dev-utils/logger'); +const { bundledExtensions } = require('./bundled-extensions'); +const configurePHP = require('./configure'); +const { getEnabledExtensions } = require('./extensions'); +const { installPhp } = require('./index'); + +/** + * @type {() => import('listr2').ListrTask} + */ +const validatePHPInstallation = () => ({ + title: 'Validating PHP installation', + task: async (ctx, task) => { + const enabledExtensions = await getEnabledExtensions(ctx.config); + const enabledExtensionsKeys = Object.keys(enabledExtensions); + + enabledExtensionsKeys.push('fpm'); + + if (enabledExtensionsKeys.some((ext) => ext.includes('mysql'))) { + enabledExtensionsKeys.push('mysql'); + } + + const missingBundledExtensions = bundledExtensions.filter( + (ext) => !enabledExtensionsKeys.some( + (ex) => ex.toLowerCase() === ext.toLowerCase() + ) + ); + + if (missingBundledExtensions.length > 0) { + const selectedOption = await task.prompt({ + type: 'Select', + message: `Your PHP version compiled by PHPBrew is missing important extensions that are bundled by default by ${logger.style.misc('magento-scripts')} +Maybe you ran PHPBrew by yourself? + +Please, consider recompiling PHP using ${logger.style.misc('magento-scripts')}. + +${logger.style.command('npm start -- --recompile-php')} +`, + choices: [ + { + name: 'recompile-php', + message: `I want ${logger.style.misc('magento-scripts')} to recompile PHP` + }, + { + name: 'skip', + message: 'I am sure that it is okay and want to continue with this setup' + } + ] + }); + + if (selectedOption === 'skip') { + task.skip('User skipped PHP recompilation'); + return; + } + + ctx.recompilePhp = true; + + return task.newListr([ + installPhp(), + configurePHP() + ], { + concurrent: false + }); + } + } +}); + +module.exports = validatePHPInstallation; diff --git a/build-packages/magento-scripts/lib/tasks/start.js b/build-packages/magento-scripts/lib/tasks/start.js index 10d68a66..ca1f981d 100644 --- a/build-packages/magento-scripts/lib/tasks/start.js +++ b/build-packages/magento-scripts/lib/tasks/start.js @@ -30,6 +30,7 @@ const enableMagentoComposerPlugins = require('./magento/enable-magento-composer- const getIsWsl = require('../util/is-wsl'); const checkForXDGOpen = require('../util/xdg-open-exists'); const { getInstanceMetadata, constants: { WEB_LOCATION_TITLE } } = require('../util/instance-metadata'); +const validatePHPInstallation = require('./php/validate-php'); /** * @type {() => import('listr2').ListrTask} @@ -113,6 +114,7 @@ const configureProject = () => ({ }) }, configurePhp(), + validatePHPInstallation(), installPrestissimo(), installMagento(), enableMagentoComposerPlugins(),