setName('vue:build'); $this->setDescription('Builds vue modules for one or more plugins.'); $this->addArgument('plugins', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Plugins whose vue modules to build. Defaults to all plugins.', []); $this->addOption('watch', null, InputOption::VALUE_NONE, 'If supplied, will watch for changes and automatically rebuild.'); $this->addOption('clear-webpack-cache', null, InputOption::VALUE_NONE); $this->addOption('print-build-command', null, InputOption::VALUE_NONE); } public function isEnabled() { return \Piwik\Development::isEnabled(); } protected function execute(InputInterface $input, OutputInterface $output) { self::checkVueCliServiceAvailable(); $this->checkNodeJsVersion($output); $clearWebpackCache = $input->getOption('clear-webpack-cache'); if ($clearWebpackCache) { $this->clearWebpackCache(); } $printBuildCommand = $input->getOption('print-build-command'); $watch = $input->getOption('watch'); $plugins = $input->getArgument('plugins'); if (empty($plugins)) { $plugins = $this->getAllPluginsWithVueLibrary(); } else { $plugins = $this->filterPluginsWithoutVueLibrary($plugins); if (empty($plugins)) { $output->writeln("No plugins to build!"); return 1; } } $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); $failed = $this->build($output, $plugins, $printBuildCommand, $watch); return $failed; } private function build(OutputInterface $output, $plugins, $printBuildCommand, $watch = false) { if ($watch) { $this->watch($plugins, $printBuildCommand, $output); return; } $failed = 0; foreach ($plugins as $plugin) { $failed += (int) $this->buildFiles($output, $plugin, $printBuildCommand); } return $failed; } private function watch($plugins, $printBuildCommand, OutputInterface $output) { $commandSingle = "BROWSERSLIST_IGNORE_OLD_DATA=1 FORCE_COLOR=1 MATOMO_CURRENT_PLUGIN=%1\$s " . self::getVueCliServiceBin() . ' build --mode=development --target lib --name ' . "%1\$s --filename=%1\$s.development --no-clean ./plugins/%1\$s/vue/src/index.ts --dest ./plugins/%1\$s/vue/dist --watch &"; $command = ''; foreach ($plugins as $plugin) { $command .= sprintf($commandSingle, $plugin) . ' '; } if ($printBuildCommand) { $output->writeln("$command"); return; } passthru($command); } private function buildFiles(OutputInterface $output, $plugin, $printBuildCommand) { $command = "BROWSERSLIST_IGNORE_OLD_DATA=1 FORCE_COLOR=1 MATOMO_CURRENT_PLUGIN=$plugin " . self::getVueCliServiceBin() . ' build --target lib --name ' . $plugin . " ./plugins/$plugin/vue/src/index.ts --dest ./plugins/$plugin/vue/dist"; if ($printBuildCommand) { $output->writeln("$command"); return 0; } $this->clearPluginTypes($plugin); $output->writeln("Building $plugin..."); if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { passthru($command, $returnCode); } else { exec($command, $cmdOutput, $returnCode); if ($returnCode != 0 || stripos(implode("\n", $cmdOutput), 'warning') !== false ) { $output->writeln("Failed:\n"); $output->writeln($cmdOutput); $output->writeln(""); } } @unlink(PIWIK_INCLUDE_PATH . "/plugins/$plugin/vue/dist/$plugin.common.js"); @unlink(PIWIK_INCLUDE_PATH . "/plugins/$plugin/vue/dist/$plugin.common.js.map"); @unlink(PIWIK_INCLUDE_PATH . "/plugins/$plugin/vue/dist/demo.html"); // delete cjs webpack chunks shell_exec("rm " . PIWIK_INCLUDE_PATH . "/plugins/$plugin/vue/dist/$plugin.common.*.js* 2> /dev/null"); return $returnCode != 0; } private function getAllPluginsWithVueLibrary() { $pluginsDir = PIWIK_INCLUDE_PATH . '/plugins'; $plugins = scandir($pluginsDir); return $this->filterPluginsWithoutVueLibrary($plugins, $isAll = true); } private function filterPluginsWithoutVueLibrary($plugins, $isAll = false) { $pluginsDir = PIWIK_INCLUDE_PATH . '/plugins'; $pluginsWithVue = []; $logger = StaticContainer::get(LoggerInterface::class); foreach ($plugins as $plugin) { $pluginDirPath = $pluginsDir . '/' . $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 webpack config, cannot build it.", ['plugin' => $plugin]); continue; } $pluginsWithVue[] = $plugin; } return $pluginsWithVue; } public static function getVueCliServiceBin() { return PIWIK_INCLUDE_PATH . "/node_modules/@vue/cli-service/bin/vue-cli-service.js"; } public static function checkVueCliServiceAvailable() { $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() { $path = PIWIK_INCLUDE_PATH . '/node_modules/.cache'; Filesystem::unlinkRecursive($path, true); } private function clearPluginTypes($plugin) { $path = PIWIK_INCLUDE_PATH . '/@types/' . $plugin; Filesystem::unlinkRecursive($path, true); } private function checkNodeJsVersion(OutputInterface $output) { $nodeVersion = ltrim(trim(`node -v`), 'v'); $npmVersion = ltrim(trim(`npm -v`), 'v'); if (version_compare($nodeVersion, self::RECOMMENDED_NODE_VERSION, '<')) { $output->writeln(sprintf("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.", self::RECOMMENDED_NODE_VERSION, $nodeVersion)); } if (version_compare($npmVersion, self::RECOMMENDED_NPM_VERSION, '<')) { $output->writeln(sprintf("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", self::RECOMMENDED_NPM_VERSION, $npmVersion, 'npm install -g npm@latest')); } } }