public function execute() { if (!class_exists(Validator::class)) { $this->error('The JsonSchema library cannot be found, please install it through composer.', 1); } elseif (!class_exists(SpdxLicenses::class)) { $this->error('The spdx-licenses library cannot be found, please install it through composer.', 1); } $path = $this->getArg(0); $data = json_decode(file_get_contents($path)); if (!is_object($data)) { $this->error("{$path} is not a valid JSON file.", 1); } if (!isset($data->manifest_version)) { $this->output("Warning: No manifest_version set, assuming 1.\n"); // For backwards-compatability assume 1 $data->manifest_version = 1; } $version = $data->manifest_version; if ($version !== ExtensionRegistry::MANIFEST_VERSION) { $schemaPath = dirname(__DIR__) . "/docs/extension.schema.v{$version}.json"; } else { $schemaPath = dirname(__DIR__) . '/docs/extension.schema.json'; } if ($version < ExtensionRegistry::OLDEST_MANIFEST_VERSION || $version > ExtensionRegistry::MANIFEST_VERSION) { $this->error("Error: {$path} is using a non-supported schema version, it should use " . ExtensionRegistry::MANIFEST_VERSION, 1); } elseif ($version < ExtensionRegistry::MANIFEST_VERSION) { $this->output("Warning: {$path} is using a deprecated schema, and should be updated to " . ExtensionRegistry::MANIFEST_VERSION . "\n"); } $licenseError = false; // Check if it's a string, if not, schema validation will display an error if (isset($data->{'license-name'}) && is_string($data->{'license-name'})) { $licenses = new SpdxLicenses(); $valid = $licenses->validate($data->{'license-name'}); if (!$valid) { $licenseError = '[license-name] Invalid SPDX license identifier, ' . 'see <https://spdx.org/licenses/>'; } } $validator = new Validator(); $validator->check($data, (object) ['$ref' => 'file://' . $schemaPath]); if ($validator->isValid() && !$licenseError) { $this->output("{$path} validates against the version {$version} schema!\n"); } else { foreach ($validator->getErrors() as $error) { $this->output("[{$error['property']}] {$error['message']}\n"); } if ($licenseError) { $this->output("{$licenseError}\n"); } $this->error("{$path} does not validate.", 1); } }
/** * @dataProvider providePassesValidation * @param string $path Path to thing's json file */ public function testPassesValidation($path) { $data = json_decode(file_get_contents($path)); $this->assertInstanceOf('stdClass', $data, "{$path} is not valid JSON"); $this->assertObjectHasAttribute('manifest_version', $data, "{$path} does not have manifest_version set."); $version = $data->manifest_version; if ($version !== ExtensionRegistry::MANIFEST_VERSION) { $schemaPath = __DIR__ . "/../../../docs/extension.schema.v{$version}.json"; } else { $schemaPath = __DIR__ . '/../../../docs/extension.schema.json'; } // Not too old $this->assertTrue($version >= ExtensionRegistry::OLDEST_MANIFEST_VERSION, "{$path} is using a non-supported schema version"); // Not too new $this->assertTrue($version <= ExtensionRegistry::MANIFEST_VERSION, "{$path} is using a non-supported schema version"); $licenseError = false; if (class_exists(SpdxLicenses::class) && isset($data->{'license-name'}) && is_string($data->{'license-name'})) { $licenses = new SpdxLicenses(); $valid = $licenses->validate($data->{'license-name'}); if (!$valid) { $licenseError = '[license-name] Invalid SPDX license identifier, ' . 'see <https://spdx.org/licenses/>'; } } $validator = new Validator(); $validator->check($data, (object) ['$ref' => 'file://' . $schemaPath]); if ($validator->isValid() && !$licenseError) { // All good. $this->assertTrue(true); } else { $out = "{$path} did pass validation.\n"; foreach ($validator->getErrors() as $error) { $out .= "[{$error['property']}] {$error['message']}\n"; } if ($licenseError) { $out .= "{$licenseError}\n"; } $this->assertTrue(false, $out); } }
/** * Validates the config, and returns the result. * * @param string $file The path to the file * @param int $arrayLoaderValidationFlags Flags for ArrayLoader validation * * @return array a triple containing the errors, publishable errors, and warnings */ public function validate($file, $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL) { $errors = array(); $publishErrors = array(); $warnings = array(); // validate json schema $laxValid = false; try { $json = new JsonFile($file, null, $this->io); $manifest = $json->read(); $json->validateSchema(JsonFile::LAX_SCHEMA); $laxValid = true; $json->validateSchema(); } catch (JsonValidationException $e) { foreach ($e->getErrors() as $message) { if ($laxValid) { $publishErrors[] = $message; } else { $errors[] = $message; } } } catch (\Exception $e) { $errors[] = $e->getMessage(); return array($errors, $publishErrors, $warnings); } // validate actual data if (!empty($manifest['license'])) { // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer if (is_array($manifest['license'])) { foreach ($manifest['license'] as $key => $license) { if ('proprietary' === $license) { unset($manifest['license'][$key]); } } } $licenseValidator = new SpdxLicenses(); if ('proprietary' !== $manifest['license'] && array() !== $manifest['license'] && !$licenseValidator->validate($manifest['license'])) { $warnings[] = sprintf('License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . "\nIf the software is closed-source, you may use \"proprietary\" as license.", json_encode($manifest['license'])); } } else { $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.'; } if (isset($manifest['version'])) { $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.'; } if (!empty($manifest['name']) && preg_match('{[A-Z]}', $manifest['name'])) { $suggestName = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']); $suggestName = strtolower($suggestName); $publishErrors[] = sprintf('Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.', $manifest['name'], $suggestName); } if (!empty($manifest['type']) && $manifest['type'] == 'composer-installer') { $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation."; } // check for require-dev overrides if (isset($manifest['require']) && isset($manifest['require-dev'])) { $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']); if (!empty($requireOverrides)) { $plural = count($requireOverrides) > 1 ? 'are' : 'is'; $warnings[] = implode(', ', array_keys($requireOverrides)) . " {$plural} required both in require and require-dev, this can lead to unexpected behavior"; } } // check for commit references $require = isset($manifest['require']) ? $manifest['require'] : array(); $requireDev = isset($manifest['require-dev']) ? $manifest['require-dev'] : array(); $packages = array_merge($require, $requireDev); foreach ($packages as $package => $version) { if (preg_match('/#/', $version) === 1) { $warnings[] = sprintf('The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.', $package); } } // check for empty psr-0/psr-4 namespace prefixes if (isset($manifest['autoload']['psr-0'][''])) { $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; } if (isset($manifest['autoload']['psr-4'][''])) { $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance"; } try { $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags); if (!isset($manifest['version'])) { $manifest['version'] = '1.0.0'; } if (!isset($manifest['name'])) { $manifest['name'] = 'dummy/dummy'; } $loader->load($manifest); } catch (InvalidPackageException $e) { $errors = array_merge($errors, $e->getErrors()); } $warnings = array_merge($warnings, $loader->getWarnings()); return array($errors, $publishErrors, $warnings); }
/** * Prints the licenses of a package with metadata * * @param CompletePackageInterface $package */ protected function printLicenses(CompletePackageInterface $package) { $spdxLicenses = new SpdxLicenses(); $licenses = $package->getLicense(); $io = $this->getIO(); foreach ($licenses as $licenseId) { $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url if (!$license) { $out = $licenseId; } else { // is license OSI approved? if ($license[1] === true) { $out = sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]); } else { $out = sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]); } } $io->write('<info>license</info> : ' . $out); } }
/** * Compiles composer into a single phar file * * @param string $pharFile The full path to the file to create * @throws \RuntimeException */ public function compile($pharFile = 'composer.phar') { if (file_exists($pharFile)) { unlink($pharFile); } $process = new Process('git log --pretty="%H" -n1 HEAD', __DIR__); if ($process->run() != 0) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } $this->version = trim($process->getOutput()); $process = new Process('git log -n1 --pretty=%ci HEAD', __DIR__); if ($process->run() != 0) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } $this->versionDate = new \DateTime(trim($process->getOutput())); $this->versionDate->setTimezone(new \DateTimeZone('UTC')); $process = new Process('git describe --tags --exact-match HEAD'); if ($process->run() == 0) { $this->version = trim($process->getOutput()); } else { // get branch-alias defined in composer.json for dev-master (if any) $localConfig = __DIR__ . '/../../composer.json'; $file = new JsonFile($localConfig); $localConfig = $file->read(); if (isset($localConfig['extra']['branch-alias']['dev-master'])) { $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-master']; } } $phar = new \Phar($pharFile, 0, 'composer.phar'); $phar->setSignatureAlgorithm(\Phar::SHA1); $phar->startBuffering(); $finderSort = function ($a, $b) { return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/')); }; $finder = new Finder(); $finder->files()->ignoreVCS(true)->name('*.php')->notName('Compiler.php')->notName('ClassLoader.php')->in(__DIR__ . '/..')->sort($finderSort); foreach ($finder as $file) { $this->addFile($phar, $file); } $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), false); $finder = new Finder(); $finder->files()->name('*.json')->in(__DIR__ . '/../../res')->in(SpdxLicenses::getResourcesDir())->sort($finderSort); foreach ($finder as $file) { $this->addFile($phar, $file, false); } $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/seld/cli-prompt/res/hiddeninput.exe'), false); $finder = new Finder(); $finder->files()->ignoreVCS(true)->name('*.php')->name('LICENSE')->exclude('Tests')->exclude('tests')->exclude('docs')->in(__DIR__ . '/../../vendor/symfony/')->in(__DIR__ . '/../../vendor/seld/jsonlint/')->in(__DIR__ . '/../../vendor/seld/cli-prompt/')->in(__DIR__ . '/../../vendor/justinrainbow/json-schema/')->in(__DIR__ . '/../../vendor/composer/spdx-licenses/')->in(__DIR__ . '/../../vendor/composer/semver/')->sort($finderSort); foreach ($finder as $file) { $this->addFile($phar, $file); } $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/autoload.php')); $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_namespaces.php')); $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_psr4.php')); $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_classmap.php')); $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_real.php')); if (file_exists(__DIR__ . '/../../vendor/composer/include_paths.php')) { $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/include_paths.php')); } $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/ClassLoader.php')); $this->addComposerBin($phar); // Stubs $phar->setStub($this->getStub()); $phar->stopBuffering(); // disabled for interoperability with systems without gzip ext // $phar->compressFiles(\Phar::GZ); $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../LICENSE'), false); unset($phar); // re-sign the phar with reproducible timestamp / signature $util = new Timestamps($pharFile); $util->updateTimestamps($this->versionDate); $util->save($pharFile, \Phar::SHA1); }