Download plugins/CoreVue/Commands/Build.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 14 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/plugins/CoreVue/Commands/Build.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/plugins/CoreVue/Commands/Build.php
-
curl -L -o Build.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/plugins/CoreVue/Commands/Build.php
14 kB
| /** | |
| * Matomo - free/libre analytics platform | |
| * | |
| * @link https://matomo.org | |
| * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later | |
| */ | |
| namespace Piwik\Plugins\CoreVue\Commands; | |
| use Piwik\AssetManager\UIAssetFetcher\PluginUmdAssetFetcher; | |
| use Piwik\Container\StaticContainer; | |
| use Piwik\Filesystem; | |
| use Piwik\Plugin\ConsoleCommand; | |
| use Piwik\Log\LoggerInterface; | |
| use Piwik\Plugin\Manager; | |
| class Build extends ConsoleCommand | |
| { | |
| public const RECOMMENDED_NODE_VERSION = '16.0.0'; | |
| public const RECOMMENDED_NPM_VERSION = '7.0.0'; | |
| public const RETRY_COUNT = 2; | |
| protected function configure() | |
| { | |
| $this->setName('vue:build'); | |
| $this->setDescription('Builds vue modules for one or more plugins.'); | |
| $this->addOptionalArgument('plugins', 'Plugins whose vue modules to build. Defaults to all plugins.', [], true); | |
| $this->addNoValueOption('bail', null, 'If supplied, will exit immediately.'); | |
| $this->addNoValueOption('watch', null, 'If supplied, will watch for changes and automatically rebuild.'); | |
| $this->addNoValueOption('clear-webpack-cache'); | |
| $this->addNoValueOption('print-build-command'); | |
| } | |
| public function isEnabled() | |
| { | |
| return \Piwik\Development::isEnabled(); | |
| } | |
| protected function doExecute(): int | |
| { | |
| $input = $this->getInput(); | |
| $output = $this->getOutput(); | |
| self::checkVueCliServiceAvailable(); | |
| $this->checkNodeJsVersion(); | |
| $clearWebpackCache = $input->getOption('clear-webpack-cache'); | |
| if ($clearWebpackCache) { | |
| $this->clearWebpackCache(); | |
| } | |
| $printBuildCommand = $input->getOption('print-build-command'); | |
| $watch = $input->getOption('watch'); | |
| $allPluginNames = $this->getAllPlugins(); | |
| $allPluginNames = $this->filterPluginsWithoutVueLibrary($allPluginNames, true); | |
| $plugins = $input->getArgument('plugins'); | |
| if (empty($plugins)) { | |
| $plugins = $allPluginNames; | |
| $output->writeln("<info>Going to build all plugins with Vue libraries: " | |
| . implode(', ', $plugins)); | |
| } else { | |
| $plugins = $this->filterPluginsWithoutVueLibrary($plugins); | |
| } | |
| if (empty($plugins)) { | |
| $output->writeln("<error>No plugins to build!</error>"); | |
| return self::FAILURE; | |
| } | |
| $plugins = $this->ensureUntranspiledPluginDependenciesArePresent($plugins); | |
| $plugins = PluginUmdAssetFetcher::orderPluginsByPluginDependencies($plugins); | |
| // remove webpack cache since it can result in strange builds if present | |
| Filesystem::unlinkRecursive(PIWIK_INCLUDE_PATH . '/node_modules/.cache', true); | |
| $bail = $input->getOption('bail'); | |
| $failed = $this->build($plugins, $printBuildCommand, $allPluginNames, $watch, $bail); | |
| return $failed > 0 ? self::FAILURE : self::SUCCESS; | |
| } | |
| private function ensureUntranspiledPluginDependenciesArePresent(array $plugins): array | |
| { | |
| $pluginDependenciesToAdd = []; | |
| foreach ($plugins as $plugin) { | |
| $dependencies = PluginUmdAssetFetcher::getPluginDependencies($plugin); | |
| foreach ($dependencies as $dependency) { | |
| if (!$this->isTypeOutputPresent($dependency)) { | |
| $pluginDependenciesToAdd[] = $dependency; | |
| } | |
| } | |
| } | |
| return array_unique(array_merge($plugins, $pluginDependenciesToAdd)); | |
| } | |
| private function isTypeOutputPresent(string $dependency): bool | |
| { | |
| $typeDirectory = PIWIK_INCLUDE_PATH . '/@types/' . $dependency . '/index.d.ts'; | |
| return is_file($typeDirectory); | |
| } | |
| private function build(array $plugins, bool $printBuildCommand, array $allPluginNames, bool $watch = false, bool $bail = false): int | |
| { | |
| if ($watch) { | |
| $this->watch($plugins, $printBuildCommand, $allPluginNames); | |
| return 0; | |
| } | |
| $failed = 0; | |
| foreach ($plugins as $plugin) { | |
| $buildFailed = (int) $this->buildFiles($plugin, $printBuildCommand, $allPluginNames); | |
| if ($buildFailed && $bail) { | |
| $this->getOutput()->writeln("<error>Build failed, bailing.</error>"); | |
| return $failed; | |
| } | |
| $failed += $buildFailed; | |
| } | |
| return $failed; | |
| } | |
| private function watch(array $plugins, bool $printBuildCommand, array $allPluginNames): void | |
| { | |
| $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 ' | |
| . "%1\$s --filename=%1\$s.development --no-clean %2\$s/vue/src/index.ts --dest %2\$s/vue/dist --watch &"; | |
| $command = ''; | |
| foreach ($plugins as $plugin) { | |
| $pluginDirPath = Manager::getRelativePluginDirectory($plugin); | |
| $command .= sprintf($commandSingle, $plugin, $pluginDirPath) . ' '; | |
| } | |
| if ($printBuildCommand) { | |
| $this->getOutput()->writeln("<comment>$command</comment>"); | |
| return; | |
| } | |
| passthru($command); | |
| } | |
| private function buildFiles(string $plugin, bool $printBuildCommand, array $allPluginNames): int | |
| { | |
| $output = $this->getOutput(); | |
| $pluginDirPath = Manager::getRelativePluginDirectory($plugin); | |
| $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 | |
| . " $pluginDirPath/vue/src/index.ts --dest $pluginDirPath/vue/dist"; | |
| if ($printBuildCommand) { | |
| $output->writeln("<comment>$command</comment>"); | |
| return 0; | |
| } | |
| $this->clearPluginTypes($plugin); | |
| $output->writeln("<comment>Building $plugin...</comment>"); | |
| if ($output->isVerbose()) { | |
| passthru($command, $returnCode); | |
| } else { | |
| $attempts = 0; | |
| while ($attempts < self::RETRY_COUNT) { | |
| exec($command, $cmdOutput, $returnCode); | |
| $concattedOutput = implode("\n", $cmdOutput); | |
| if ($this->isTypeScriptRaceConditionInOutput($plugin, $concattedOutput)) { | |
| $output->writeln("<comment>The TypeScript compiler encountered a race condition when compiling " | |
| . "files (files that exist were not found), retrying.</comment>"); | |
| ++$attempts; | |
| continue; | |
| } | |
| if ( | |
| $returnCode != 0 | |
| || stripos($concattedOutput, 'warning') !== false | |
| ) { | |
| $output->writeln("<error>Failed:</error>\n"); | |
| $output->writeln($cmdOutput); | |
| $output->writeln(""); | |
| } | |
| break; | |
| } | |
| } | |
| @unlink("$pluginDirPath/vue/dist/$plugin.common.js"); | |
| @unlink("$pluginDirPath/vue/dist/$plugin.common.js.map"); | |
| @unlink("$pluginDirPath/vue/dist/demo.html"); | |
| // delete cjs webpack chunks | |
| shell_exec("rm " . "$pluginDirPath/vue/dist/$plugin.common.*.js* 2> /dev/null"); | |
| return $returnCode != 0; | |
| } | |
| private function filterPluginsWithoutVueLibrary(array $plugins, bool $isAll = false): array | |
| { | |
| $pluginsWithVue = []; | |
| $logger = StaticContainer::get(LoggerInterface::class); | |
| foreach ($plugins as $plugin) { | |
| $pluginDirPath = Manager::getPluginDirectory($plugin); | |
| $vueDir = $pluginDirPath . '/vue'; | |
| if (!is_dir($vueDir)) { | |
| if (!$isAll) { | |
| $logger->error("Cannot find vue library for plugin {plugin}, nothing to build.", ['plugin' => $plugin]); | |
| } | |
| continue; | |
| } | |
| $vueIndexFile = $vueDir . '/src/index.ts'; | |
| if (!is_file($vueIndexFile)) { | |
| $logger->warning("NOTE: Plugin {plugin} has a vue folder but no index.ts, cannot build it.", ['plugin' => $plugin]); | |
| continue; | |
| } | |
| $pluginsWithVue[] = $plugin; | |
| } | |
| return $pluginsWithVue; | |
| } | |
| public static function getVueCliServiceBin(): string | |
| { | |
| return PIWIK_INCLUDE_PATH . "/node_modules/@vue/cli-service/bin/vue-cli-service.js"; | |
| } | |
| public static function getVueCliServiceProxyBin(): string | |
| { | |
| return PIWIK_INCLUDE_PATH . "/plugins/CoreVue/scripts/cli-service-proxy.js"; | |
| } | |
| public static function checkVueCliServiceAvailable(): void | |
| { | |
| $vueCliBin = self::getVueCliServiceBin(); | |
| if (!is_file($vueCliBin)) { | |
| throw new \Exception("Cannot find vue cli bin file, did you forget to run `npm install`?"); | |
| } | |
| } | |
| private function clearWebpackCache(): void | |
| { | |
| $path = PIWIK_INCLUDE_PATH . '/node_modules/.cache'; | |
| Filesystem::unlinkRecursive($path, true); | |
| } | |
| private function clearPluginTypes($plugin): void | |
| { | |
| $path = PIWIK_INCLUDE_PATH . '/@types/' . $plugin; | |
| Filesystem::unlinkRecursive($path, true); | |
| } | |
| private function checkNodeJsVersion(): void | |
| { | |
| $output = $this->getOutput(); | |
| $nodeVersion = ltrim(trim(`node -v`), 'v'); | |
| $npmVersion = ltrim(trim(`npm -v`), 'v'); | |
| if (version_compare($nodeVersion, self::RECOMMENDED_NODE_VERSION, '<')) { | |
| $output->writeln(sprintf( | |
| "<comment>The recommended node version for working with Vue is version %s or " | |
| . "greater and it looks like you're using %s. Building Vue files may not work with an older version, so " | |
| . "we recommend upgrading. nvm can be used to easily install new node versions.</comment>", | |
| self::RECOMMENDED_NODE_VERSION, | |
| $nodeVersion | |
| )); | |
| } | |
| if (version_compare($npmVersion, self::RECOMMENDED_NPM_VERSION, '<')) { | |
| $output->writeln(sprintf( | |
| "<comment>The recommended npm version for working with Vue is version %s " | |
| . "or greater and it looks like you're using %s. Using an older version may result in improper " | |
| . "dependencies being used, so we recommend upgrading. You can upgrade to the latest version with the " | |
| . "command %s</comment>", | |
| self::RECOMMENDED_NPM_VERSION, | |
| $npmVersion, | |
| 'npm install -g npm@latest' | |
| )); | |
| } | |
| } | |
| private function isTypeScriptRaceConditionInOutput(string $plugin, string $concattedOutput): bool | |
| { | |
| // Console output can contain ANSI escape sequences; remove them first to make regex matching stable. | |
| // Example: | |
| // "\x1b[41m ERROR \x1b[49m ... TS2307: Cannot find module ..." | |
| // becomes: | |
| // " ERROR ... TS2307: Cannot find module ..." | |
| $normalizedOutput = preg_replace('/\x1b\[[0-9;]*m/', '', $concattedOutput); | |
| if (empty($normalizedOutput)) { | |
| return false; | |
| } | |
| // Match TS2307 blocks and capture: | |
| // 1) the importer file path | |
| // 2) the missing module path | |
| // so we can verify whether the target actually exists on disk. | |
| $matches = []; | |
| preg_match_all( | |
| '/in\s+([^\n]+)\(\d+,\d+\)\s*\n\s*TS2307:\s+Cannot find module \'([^\']+)\' or its corresponding type declarations\./m', | |
| $normalizedOutput, | |
| $matches, | |
| PREG_SET_ORDER | |
| ); | |
| foreach ($matches as $match) { | |
| $importerFile = trim($match[1]); | |
| $missingModule = $match[2]; | |
| if ($this->isRelativeImport($missingModule)) { | |
| // Relative imports are resolved from the importer directory. | |
| // If the path exists, this is likely a transient TS resolver race and should be retried. | |
| $resolvedPath = dirname($importerFile) . DIRECTORY_SEPARATOR . $missingModule; | |
| if (file_exists($resolvedPath)) { | |
| return true; | |
| } | |
| continue; | |
| } | |
| // Keep legacy behavior for non-relative plugin imports (e.g. "SomePluginComponent"). | |
| $filePath = Manager::getPluginDirectory($plugin) . '/vue/src/' . $missingModule; | |
| if (file_exists($filePath)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| private function isRelativeImport(string $importPath): bool | |
| { | |
| return strpos($importPath, './') === 0 || strpos($importPath, '../') === 0; | |
| } | |
| private function getAllPlugins(): array | |
| { | |
| $pluginDirectories = array_merge( | |
| $GLOBALS['MATOMO_PLUGIN_DIRS'] ?? [], | |
| [ | |
| [ | |
| 'pluginsPathAbsolute' => PIWIK_INCLUDE_PATH . '/plugins', | |
| 'webrootDirRelativeToMatomo' => '.', | |
| ], | |
| ] | |
| ); | |
| $allPlugins = []; | |
| foreach ($pluginDirectories as $pluginDirectoryInfo) { | |
| $absolutePath = $pluginDirectoryInfo['pluginsPathAbsolute']; | |
| foreach (scandir($absolutePath) as $subdirectory) { | |
| $wholePath = $absolutePath . DIRECTORY_SEPARATOR . $subdirectory; | |
| if ( | |
| is_dir($wholePath) | |
| && $subdirectory !== '.' | |
| && $subdirectory !== '..' | |
| ) { | |
| $allPlugins[] = $subdirectory; | |
| } | |
| } | |
| } | |
| return $allPlugins; | |
| } | |
| } | |