diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 73ab7fc34b4..dcd02b6d2e1 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -22,7 +22,7 @@ web_environment: - MATOMO_DATABASE_USERNAME=db - MATOMO_DATABASE_PASSWORD=db - MATOMO_DATABASE_DBNAME=db -nodejs_version: "16" +nodejs_version: "20" corepack_enable: true disable_upload_dirs_warning: true diff --git a/.github/workflows/buildvue.yml b/.github/workflows/buildvue.yml index c71de4e07d9..edf2bdb3974 100644 --- a/.github/workflows/buildvue.yml +++ b/.github/workflows/buildvue.yml @@ -73,7 +73,7 @@ jobs: - name: Setup node uses: actions/setup-node@v6 with: - node-version: '16' + node-version: '20' cache: 'npm' if: steps.vars.outputs.branch != '' && steps.vuecheck.outputs.vue_modified == '1' - run: npm install diff --git a/.github/workflows/matomo-tests.yml b/.github/workflows/matomo-tests.yml index 4921dff5d73..93e84068afa 100644 --- a/.github/workflows/matomo-tests.yml +++ b/.github/workflows/matomo-tests.yml @@ -113,7 +113,7 @@ jobs: with: test-type: 'JS' php-version: '7.2' - node-version: '12' + node-version: '20' Client: runs-on: ubuntu-24.04 timeout-minutes: 15 @@ -129,7 +129,7 @@ jobs: uses: matomo-org/github-action-tests@main with: test-type: 'Client' - node-version: '16' + node-version: '20' mysql-service: false UI: runs-on: ubuntu-24.04 @@ -151,7 +151,7 @@ jobs: ui-test-options: '--num-test-groups=4 --test-group=${{ matrix.parts }}' test-type: 'UI' php-version: '7.2' - node-version: '16' + node-version: '20' redis-service: true artifacts-pass: ${{ secrets.ARTIFACTS_PASS }} upload-artifacts: true diff --git a/.github/workflows/run-single-ui-test-suite.yml b/.github/workflows/run-single-ui-test-suite.yml index 653f8f3d649..21593ef9eb8 100644 --- a/.github/workflows/run-single-ui-test-suite.yml +++ b/.github/workflows/run-single-ui-test-suite.yml @@ -47,7 +47,7 @@ jobs: ui-test-options: '${{ inputs.test_suite_name }}' test-type: 'UI' php-version: '7.2' - node-version: '16' + node-version: '20' redis-service: true artifacts-pass: ${{ secrets.ARTIFACTS_PASS }} - upload-artifacts: true \ No newline at end of file + upload-artifacts: true diff --git a/package.json b/package.json index eb482ee83c6..11c6c1c2687 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ }, "homepage": "https://github.com/matomo-org/matomo#readme", "engines": { - "node": ">=16.0.0 <17.0.0" + "node": ">=20.0.0 <21.0.0", + "npm": ">=10.0.0" }, "dependencies": { "@materializecss/materialize": "^1.1.0", diff --git a/plugins/CoreVue/Commands/Build.php b/plugins/CoreVue/Commands/Build.php index f5c1b314a22..5b3f088f25b 100644 --- a/plugins/CoreVue/Commands/Build.php +++ b/plugins/CoreVue/Commands/Build.php @@ -18,8 +18,8 @@ class Build extends ConsoleCommand { - public const RECOMMENDED_NODE_VERSION = '16.0.0'; - public const RECOMMENDED_NPM_VERSION = '7.0.0'; + public const RECOMMENDED_NODE_VERSION = '20.0.0'; + public const RECOMMENDED_NPM_VERSION = '10.0.0'; public const RETRY_COUNT = 2; protected function configure() @@ -127,10 +127,12 @@ private function build(array $plugins, bool $printBuildCommand, array $allPlugin private function watch(array $plugins, bool $printBuildCommand, array $allPluginNames): void { + $nodeCompatibilityEnv = $this->getNodeCompatibilityEnv(); + $commandSingle = 'cd ' . PIWIK_INCLUDE_PATH . ' && ' . "BROWSERSLIST_IGNORE_OLD_DATA=1 FORCE_COLOR=1 MATOMO_CURRENT_PLUGIN=%2\$s " . 'MATOMO_ALL_PLUGINS=' . implode(',', $allPluginNames) . ' ' - . 'node ' . self::getVueCliServiceProxyBin() . ' build --mode=development --target lib --name ' + . $nodeCompatibilityEnv . 'node ' . self::getVueCliServiceProxyBin() . ' build --mode=development --target lib --name ' . "%1\$s --filename=%1\$s.development --no-clean %2\$s/vue/src/index.ts --dest %2\$s/vue/dist --watch &"; $command = ''; @@ -151,11 +153,12 @@ private function buildFiles(string $plugin, bool $printBuildCommand, array $allP { $output = $this->getOutput(); $pluginDirPath = Manager::getRelativePluginDirectory($plugin); + $nodeCompatibilityEnv = $this->getNodeCompatibilityEnv(); $command = 'cd ' . PIWIK_INCLUDE_PATH . ' && ' . "BROWSERSLIST_IGNORE_OLD_DATA=1 FORCE_COLOR=1 MATOMO_CURRENT_PLUGIN=$pluginDirPath " . 'MATOMO_ALL_PLUGINS=' . implode(',', $allPluginNames) . ' ' - . 'node ' . self::getVueCliServiceProxyBin() . ' build --target lib --name ' . $plugin + . $nodeCompatibilityEnv . 'node ' . self::getVueCliServiceProxyBin() . ' build --target lib --name ' . $plugin . " $pluginDirPath/vue/src/index.ts --dest $pluginDirPath/vue/dist"; if ($printBuildCommand) { @@ -292,6 +295,21 @@ private function checkNodeJsVersion(): void } } + private function getNodeCompatibilityEnv(): string + { + $nodeVersion = ltrim(trim(`node -v`), 'v'); + if (version_compare($nodeVersion, '17.0.0', '<')) { + return ''; + } + + $nodeOptions = getenv('NODE_OPTIONS') ?: ''; + if (strpos($nodeOptions, '--openssl-legacy-provider') === false) { + $nodeOptions = trim($nodeOptions . ' --openssl-legacy-provider'); + } + + return 'NODE_OPTIONS=' . escapeshellarg($nodeOptions) . ' '; + } + private function isTypeScriptRaceConditionInOutput(string $plugin, string $concattedOutput): bool { // Console output can contain ANSI escape sequences; remove them first to make regex matching stable.