protected override void Run()
        {
            TestState = TestState.Succeeded;

            if (!SemVersion.TryParse(Context.PublishPackageInfo.version, out var thisVersion, true))
            {
                AddError("Failed to parse package version \"{0}\"", Context.PublishPackageInfo.version);
                return;
            }

            var lastFullReleaseVersion = SemVersion.Parse(Context.PreviousPackageInfo != null ? Context.PreviousPackageInfo.version : "0.0.0");

            // if previous version's major is greater than 0, then all is good
            // if previous version's major is 0 and trying to release between [0, 1].y.z, then all is good
            if (lastFullReleaseVersion.Major >= 1 || thisVersion.Major - lastFullReleaseVersion.Major <= 1)
            {
                return;
            }

            var message = "Invalid major version " + thisVersion + " when publishing to production registry.";

            if (lastFullReleaseVersion == "0.0.0")
            {
                message += $" There has never been a full release of this package. The major should be 0 or 1.";
            }
            else
            {
                message += "The next release cannot be more than 1 major above the latest full release (" +
                           lastFullReleaseVersion + ").";
            }

            AddWarning(message + " {0}", ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "invalid-major-release"));
        }
Exemple #2
0
        // Generate a standard error message for project manifest field checks. This is also used during tests
        internal string CreateFieldErrorMessage(string fieldName)
        {
            string docsLink = ErrorDocumentation.GetLinkMessage(k_DocsFilePath,
                                                                "The-{fieldName}-field-in-the-project-manifest-is-not-a-valid-field-for-template-packages");

            return($"The `{fieldName}` field in the project manifest is not a valid field for template packages. Please remove this field from {Context.ProjectInfo.ManifestPath}. {docsLink}");
        }
 private void ValidateUnityAuthor(ManifestData manifestData)
 {
     if (manifestData.author != null)
     {
         // make sure it is not present in order to have a unified presentation of the author for all of our packages
         AddError("A Unity package must not have an author field. Please remove the field. {0}",
                  ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath,
                                                    "a_unity_package_must_not_have_an_author_field"));
     }
 }
        void ValidatePrimedLibrary()
        {
            // Check that Library directory of template contains primed paths
            foreach (var primedLibraryPath in k_PrimedLibraryPaths)
            {
                var packageRelativePath = Path.Combine(k_LibraryPath, primedLibraryPath);
                var fullPath            = Path.Combine(Context.PublishPackageInfo.path, packageRelativePath);

                if (!(File.Exists(fullPath) || Directory.Exists(fullPath)))
                {
                    var documentationLink = ErrorDocumentation.GetLinkMessage(
                        k_DocsFilePath, "template-is-missing-primed-library-path");
                    AddError($"Template is missing primed library path at {packageRelativePath}. " +
                             $"It should have been added automatically in the CI packing process. {documentationLink}");
                }
            }
        }
Exemple #5
0
        // Require a minor bump of the package version when the minimum Unity version requirement becomes more strict.
        // This is to ensure that we have patch versions available for the previous version of the package.
        void ValidateUnityVersionBump(ManifestData previousManifest, ManifestData currentManifest)
        {
            // This is the first version of the package.
            if (previousManifest == null)
            {
                return;
            }

            // Minimum Unity version requirement did not change.
            var currentPackageUnityVersion  = GetPackageUnityVersion(currentManifest);
            var previousPackageUnityVersion = GetPackageUnityVersion(previousManifest);

            if (currentPackageUnityVersion == previousPackageUnityVersion)
            {
                return;
            }

            // Minimum Unity version requirement became less strict.
            if (currentPackageUnityVersion < previousPackageUnityVersion)
            {
                AddWarning(
                    "The Unity version requirement is less strict than in the previous version of the package. " +
                    "Please confirm that this change is deliberate and intentional. " +
                    ErrorDocumentation.GetLinkMessage(k_DocsFilePath, k_DocsLessStrictSection));
                return;
            }

            // Major or minor version of package was bumped.
            var previousPackageVersion = SemVersion.Parse(previousManifest.version);
            var currentPackageVersion  = SemVersion.Parse(currentManifest.version);

            if (currentPackageVersion.Major > previousPackageVersion.Major ||
                currentPackageVersion.Minor > previousPackageVersion.Minor)
            {
                return;
            }

            AddUnityAuthoredConditionalError(currentManifest,
                                             "The Unity version requirement is more strict than in the previous version of the package. " +
                                             "Increment the minor version of the package to leave patch versions available for previous version. " +
                                             ErrorDocumentation.GetLinkMessage(k_DocsFilePath, k_DocsMoreStrictSection));
        }
        private void LifecycleV1VersionValidator(SemVersion packageVersionNumber, VersionTag versionTag)
        {
            if (Context.IsCore && (!versionTag.IsEmpty() || packageVersionNumber.Major < 1))
            {
                AddError("Core packages cannot be preview. " + ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "core-packages-cannot-be-preview"));
                return;
            }

            if (packageVersionNumber.Major < 1 && (versionTag.IsEmpty() || versionTag.Tag != "preview"))
            {
                AddError("In package.json, \"version\" < 1, please tag the package as " + packageVersionNumber.VersionOnly() + "-preview. " + ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "version-1-please-tag-the-package-as-xxx-preview"));
                return;
            }

            // The only pre-release tag we support is -preview
            if (!versionTag.IsEmpty() && !(versionTag.Tag == "preview" && versionTag.Feature == "" &&
                                           versionTag.Iteration <= 999999))
            {
                AddError(
                    "In package.json, \"version\": the only pre-release filter supported is \"-preview.[num < 999999]\". " + ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "version-the-only-pre-release-filter-supported-is--preview-num-999999"));
            }
        }
        // the author field is required for non-unity packages
        private void ValidateNonUnityAuthor(ManifestData manifestData)
        {
            // if authordetails is set, then author == ""
            if (String.IsNullOrEmpty(manifestData.author) && manifestData.authorDetails == null)
            {
                AddError(
                    "The `author` field is mandatory. Please add an `author` field in your package.json file",
                    ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "author_is_mandatory"));
                return;
            }

            if (!String.IsNullOrEmpty(manifestData.author) && manifestData.authorDetails == null)
            {
                return; // valid
            }

            // non unity packages should have either a string or AuthorDetails { name: ""*, email: "", url: ""}
            if (String.IsNullOrEmpty(manifestData.authorDetails.name))
            {
                AddError(
                    "Invalid `author` field. The `author` field in your package.json file can be a string or an object ( name, email, url ), where `name` is mandatory. {0}",
                    ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "author_is_invalid"));
            }
        }
        protected override void Run()
        {
            // Start by declaring victory
            TestState = TestState.Succeeded;
            var licenseFilePath          = Path.Combine(Context.PublishPackageInfo.path, Utilities.LicenseFile);
            var thirdPartyNoticeFilePath = Path.Combine(Context.PublishPackageInfo.path, Utilities.ThirdPartyNoticeFile);

            // Check that the package has a license.md file.  All packages should have one.
            if (File.Exists(licenseFilePath))
            {
                // TODO: If the license file exists, check that the copyright year is setup properly.
                CheckLicenseContent(licenseFilePath);
            }
            else if (Context.ValidationType == ValidationType.VerifiedSet)
            {
                AddWarning(string.Format("Every package must have a LICENSE.md file. {0}", ErrorDocumentation.GetLinkMessage(ErrorTypes.LicenseFileMissing)));
            }
            else
            {
                AddError(string.Format("Every package must have a LICENSE.md file. {0}", ErrorDocumentation.GetLinkMessage(ErrorTypes.LicenseFileMissing)));
            }

            // Check that the 3rd party notice file is not empty, and does not come from the starter kit.
            if (File.Exists(thirdPartyNoticeFilePath))
            {
                CheckThirdPartyNoticeContent(thirdPartyNoticeFilePath);

                // TODO: Signal to the vetting report that the package contains a 3rd party notice
            }
            else
            {
                // TODO: check that the code doesn't have any copyright headers if the 3rd party notice file is empty.
                CheckForCopyrightMaterial();
            }
        }
        private void ValidateDependencies()
        {
            // Check if there are dependencies to check and exit early
            if (Context.ProjectPackageInfo.dependencies.Count == 0)
            {
                return;
            }

            var isFeature = Context.ProjectPackageInfo.PackageType == PackageType.FeatureSet;

            // Make sure all dependencies are already published in production.
            foreach (var dependency in Context.ProjectPackageInfo.dependencies)
            {
                if (isFeature && dependency.Value != "default")
                {
                    AddError(@"In package.json for a feature, dependency ""{0}"" : ""{1}"" needs to be set to ""default""", dependency.Key, dependency.Value);
                    continue;
                }

                // Check if the dependency semver is valid before doing anything else
                SemVersion depVersion;
                if (!isFeature && !SemVersion.TryParse(dependency.Value, out depVersion))
                {
                    AddError(@"In package.json, dependency ""{0}"" : ""{1}"" needs to be a valid ""Semver"". {2}", dependency.Key, dependency.Value, ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "dependency_needs_to_be_a_valid_Semver"));
                    continue;
                }

                var dependencyInfo = Utilities.UpmListOffline(dependency.Key).FirstOrDefault();

                // Built in packages are shipped with the editor, and will therefore never be published to production.
                if (dependencyInfo != null && dependencyInfo.source == PackageSource.BuiltIn)
                {
                    continue;
                }

                // Check if this package's dependencies are in production. That is a requirement for promotion.
                // make sure to check the version actually resolved by upm and not the one necessarily listed by the package
                // If the packageId is included in Context.packageIdsForPromotion then we can skip this check, as the package
                // is expected to be promoted by another process
                var version   = dependencyInfo != null ? dependencyInfo.version : dependency.Value;
                var packageId = Utilities.CreatePackageId(dependency.Key, version);

                if (Context.ValidationType != ValidationType.VerifiedSet && (Context.packageIdsForPromotion == null || Context.packageIdsForPromotion.Length < 1 || !Context.packageIdsForPromotion.Contains(packageId)) && !Utilities.PackageExistsOnProduction(packageId))
                {
                    // ignore if  package is part of the context already
                    if (Context.ValidationType == ValidationType.Promotion || Context.ValidationType == ValidationType.AssetStore)
                    {
                        AddError("Package dependency {0} is not promoted in production. {1}", packageId, ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "package-dependency-[packageID]-is-not-published-in-production"));
                    }
                    else
                    {
                        AddWarning("Package dependency {0} must be promoted to production before this package is promoted to production. (Except for core packages)", packageId);
                    }
                }

                // only check this in CI or internal local development
                // Make sure the dependencies I ask for that exist in the project have the good version
                if (Context.ValidationType == ValidationType.CI || Context.ValidationType == ValidationType.LocalDevelopmentInternal)
                {
                    PackageInfo packageInfo = Utilities.UpmListOffline(dependency.Key).FirstOrDefault();
                    if (packageInfo != null && packageInfo.version != dependency.Value)
                    {
                        AddWarning("Package {2} depends on {0}, which is found locally but with another version. To remove this warning, in the package.json file of {2}, change the dependency of {0}@{1} to {0}@{3}.", dependency.Key, dependency.Value, Context.ProjectPackageInfo.name, packageInfo.version);
                    }
                }
            }

            // TODO: Validate the Package dependencies meet the minimum editor requirement (eg: 2018.3 minimum for package A is 2, make sure I don't use 1)
        }
        private void ValidateVersion(ManifestData manifestData)
        {
            SemVersion version;

            // Check package version, make sure it's a valid SemVer string.
            if (!SemVersion.TryParse(manifestData.version, out version))
            {
                AddError("In package.json, \"version\" needs to be a valid \"Semver\". {0}", ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "version-needs-to-be-a-valid-semver"));
            }
        }
        private void ValidateManifestData(ManifestData manifestData)
        {
            // Check the package Name, which needs to start with one of the approved company names.
            // This should probably be executed only in internal development, CI and Promotion contexts
            if (!PackageNamePrefixList.Any(namePrefix => (manifestData.name.StartsWith(namePrefix) && manifestData.name.Length > namePrefix.Length)))
            {
                AddError("In package.json, \"name\" needs to start with one of these approved company names: {0}. {1}", string.Join(", ", PackageNamePrefixList), ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "name-needs-to-start-with-one-of-these-approved-company-names"));
            }

            // There cannot be any capital letters in package names.
            if (manifestData.name.ToLower(CultureInfo.InvariantCulture) != manifestData.name)
            {
                AddError("In package.json, \"name\" cannot contain capital letters. {0}", ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "name-cannot-contain-capital-letters"));
            }

            // Check name against our regex.
            Match match = Regex.Match(manifestData.name, UpmRegex);

            if (!match.Success)
            {
                AddError("In package.json, \"name\" is not a valid name. {0}", ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "name-is-not-a-valid-name"));
            }

            // Package name cannot end with .framework, .plugin or .bundle.
            String[] strings = { ".framework", ".bundle", ".plugin" };
            foreach (var value in strings)
            {
                if (manifestData.name.EndsWith(value))
                {
                    AddError("In package.json, \"name\" cannot end with .plugin, .bundle or .framework. {0}", ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "name-cannot-end-with"));
                }
            }

            if (string.IsNullOrEmpty(manifestData.displayName))
            {
                AddError("In package.json, \"displayName\" must be set. {0}", ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "displayName-must-be-set"));
            }
            else if (manifestData.displayName.Length > MaxDisplayNameLength)
            {
                AddError("In package.json, \"displayName\" is too long. Max Length = {0}. Current Length = {1}. {2}", MaxDisplayNameLength, manifestData.displayName.Length, ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "displayName-is-too-long"));
            }
            else if (!Regex.Match(manifestData.displayName, UpmDisplayRegex).Success)
            {
                AddError("In package.json, \"displayName\" cannot have any special characters. {0}", ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "displayName-cannot-have-any-special-characters"));
            }

            // Check Description, make sure it's there, and not too short.
            if (manifestData.description.Length < MinDescriptionSize)
            {
                AddError("In package.json, \"description\" is too short. Minimum Length = {0}. Current Length = {1}. {2}", MinDescriptionSize, manifestData.description.Length, ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "description-is-too-short"));
            }

            // check unity field, if it's there
            if (!string.IsNullOrEmpty(manifestData.unity) && (manifestData.unity.Length > 6 || !Regex.Match(manifestData.unity, UnityRegex).Success))
            {
                AddError($"In package.json, \"unity\" is invalid. It should only be <MAJOR>.<MINOR> (e.g. 2018.4). Current unity = {manifestData.unity}. {ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath,  "unity-is-invalid")}");
            }

            // check unityRelease field, if it's there
            if (!string.IsNullOrEmpty(manifestData.unityRelease))
            {
                // it should be valid
                if (!Regex.Match(manifestData.unityRelease, UnityReleaseRegex).Success)
                {
                    AddError(
                        $"In package.json, \"unityRelease\" is invalid. Current unityRelease = {manifestData.unityRelease}. {ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "unityrelease-is-invalid")}");
                }

                // it should be accompanied of a unity field
                if (string.IsNullOrEmpty(manifestData.unity))
                {
                    AddError(
                        $"In package.json, \"unityRelease\" needs a \"unity\" field to be used. {ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "unityrelease-without-unity")}");
                }
            }

            // check documentation url field
            if (Context.ValidationType == ValidationType.Promotion || Context.ValidationType == ValidationType.CI)
            {
                if (!string.IsNullOrWhiteSpace(manifestData.documentationUrl))
                {
                    AddError("In package.json, \"documentationUrl\" can't be used for Unity packages.  It is a features reserved for enterprise customers.  The Unity documentation team will ensure the package's documentation is published in the appropriate fashion");
                }

                // Check if `repository.url` and `repository.revision` exist and the content is valid
                string value;
                if (!manifestData.repository.TryGetValue("url", out value) || string.IsNullOrEmpty(value))
                {
                    AddError("In package.json for a published package, there must be a \"repository.url\" field. {0}", ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "for_a_published_package_there_must_be_a_repository.url_field"));
                }
                if (!manifestData.repository.TryGetValue("revision", out value) || string.IsNullOrEmpty(value))
                {
                    AddError("In package.json for a published package, there must be a \"repository.revision\" field. {0}", ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "for_a_published_package_there_must_be_a_repository.revision_field"));
                }
            }
            else
            {
                AddInformation("Skipping Git tags check as this is a package in development.");
            }
        }
Exemple #12
0
        /// <summary>
        /// For templates, we want to make sure that Show Preview Packages or Show Pre-Release packages
        /// are not enabled.
        /// For <=2019.4 this is saved on the profile preferences and is not bound to be
        /// pre-enabled. For >2019.4 this is saved in ProjectSettings/PackageManagerSettings.asset
        /// </summary>
        private void ValidatePackageManagerSettings()
        {
            if (Context.ProjectInfo.PackageManagerSettingsValidation == null)
            {
                return;
            }

            if (Context.ProjectInfo.PackageManagerSettingsValidation.m_EnablePreviewPackages)
            {
                AddError($"Preview packages are not allowed to be enabled on template packages. Please disable the `Enable Preview Packages` option in ProjectSettings > PackageManager > Advanced Settings. {ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "preview|prerelease-packages-are-not-allowed-to-be-enabled-on-template-packages")}");
            }

            if (Context.ProjectInfo.PackageManagerSettingsValidation.m_EnablePreReleasePackages)
            {
                AddError($"PreRelease packages are not allowed to be enabled on template packages. Please disable the `Enable PreRelease Packages` option in ProjectSettings > PackageManager > Advanced Settings. {ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "preview|prerelease-packages-are-not-allowed-to-be-enabled-on-template-packages")}");
            }
        }
Exemple #13
0
        protected override void Run()
        {
            // Start by declaring victory
            TestState = TestState.Succeeded;

            // Check if the file exists first
            var changeLogPath = Path.Combine(Context.ProjectPackageInfo.path, Utilities.ChangeLogFilename);

            if (!System.IO.File.Exists(changeLogPath))
            {
                AddError("Cannot find changelog at '{0}'. Please create a '{1}' file and '{1}.meta'. {2}", changeLogPath, Utilities.ChangeLogFilename, ErrorDocumentation.GetLinkMessage(ErrorTypes.CannotFindChangelog));
                return;
            }

            SemVersion packageJsonVersion;

            if (!SemVersion.TryParse(Context.ProjectPackageInfo.version, out packageJsonVersion))
            {
                AddError("Version format '{0}' is not valid in '{1}'. Please correct the version format. {2}", Context.ProjectPackageInfo.version, Context.ProjectPackageInfo.path, ErrorDocumentation.GetLinkMessage(ErrorTypes.VersionFormatIsNotValid));
                return;
            }
            // We must strip the -build<commit> off the prerelease
            var buildInfoIndex = packageJsonVersion.Prerelease.IndexOf("build");

            if (buildInfoIndex > 0)
            {
                var cleanPrerelease = packageJsonVersion.Prerelease.Substring(0, buildInfoIndex - 1);
                packageJsonVersion = packageJsonVersion.Change(null, null, null, cleanPrerelease, null);
            }
            var packageJsonVersionNoPrelease = packageJsonVersion.Change(null, null, null, "", null);

            // We are basically searching for a string ## [Version] - YYYY-MM-DD or ## [Unreleased]
            var changeLogLineRegex = @"## \[(?<version>.*)]( - (?<date>.*))?";

            var textChangeLog = File.ReadAllLines(changeLogPath);

            // We match each line individually so we won't end up with line ending characters etc in the match
            List <Match> matches = new List <Match>();

            foreach (var line in textChangeLog)
            {
                var lineMatches = Regex.Matches(line, changeLogLineRegex);
                foreach (var match in lineMatches.Cast <Match>())
                {
                    matches.Add(match);
                }
            }
            if (matches.Count == 0)
            {
                AddError("No valid changelog entries were found. The changelog needs to follow the https://keepachangelog.org specifications (`## [x.y.z] - YYYY-MM-DD` or `## [Unreleased]`). {0}", ErrorDocumentation.GetLinkMessage(ErrorTypes.NoValidChangelogEntriesWereFound));
                return;
            }

            int   foundIndex   = -1;
            int   currentIndex = 1;
            Match found        = null;

            foreach (Match match in matches)
            {
                SemVersion versionInChangelog = null;
                string     versionString      = match.Groups["version"].ToString();
                if (versionString == "Unreleased")
                {
                    if (found == null)
                    {
                        // In case of duplicated fields etc we keep track of the first entry to so we dont just pile more errors on top
                        found      = match;
                        foundIndex = currentIndex;
                    }

                    if (currentIndex != 1)
                    {
                        AddWarningWithLine(string.Format("Unreleased section has to be the first section in the Changelog but it was number {0}. Please move it to the top or remove duplicate entries. {1}", currentIndex, ErrorDocumentation.GetLinkMessage(ErrorTypes.UnreleasedSectionFirst)), match.ToString());
                    }

                    if (Context.ValidationType == ValidationType.CI)
                    {
                        AddWarningWithLine(
                            string.Format(
                                "The package has an [unreleased] section in the changelog in {0}. This is accepted in CI, and internal publishing,"
                                + " but is not accepted when sharing externally with clients. Please curate your unreleased section to reflect the package version before promoting your package to production. {1}",
                                changeLogPath,
                                ErrorDocumentation.GetLinkMessage(ErrorTypes.UnreleasedNotAllowedInPromoting)
                                ),
                            match.ToString()
                            );
                    }

                    if (Context.ValidationType == ValidationType.Promotion)
                    {
                        AddErrorWithLine(
                            string.Format(
                                "The package has an [unreleased] section in the changelog in {0}. This is accepted in CI, and internal publishing,"
                                + " but is not accepted when sharing externally with clients. Please curate your unreleased section to reflect the package version before promoting your package to production. {1}",
                                changeLogPath,
                                ErrorDocumentation.GetLinkMessage(ErrorTypes.UnreleasedNotAllowedInPromoting)
                                ),
                            match.ToString()
                            );
                    }

                    currentIndex++;
                    continue;
                }

                if (!SemVersion.TryParse(versionString, out versionInChangelog, true))
                {
                    AddErrorWithDeprecationFallback(string.Format("Version format '{0}' is not valid in '{1}'. Please correct the version format. {2}", match.Groups["version"].ToString(), changeLogPath, ErrorDocumentation.GetLinkMessage(ErrorTypes.VersionFormatIsNotValid)), match.ToString(), foundIndex, currentIndex);
                    currentIndex++;
                    continue;
                }

                if (found == null && (versionInChangelog == packageJsonVersion || versionInChangelog == packageJsonVersionNoPrelease))
                {
                    // In case of duplicated fields etc we keep track of the first entry to so we dont just pile more errors on top
                    found      = match;
                    foundIndex = currentIndex;
                }
                DateTime date;
                string   dateFormat         = "yyyy-MM-dd";
                string[] dateWarningFormats = { "yyyy-MM-d", "yyyy-M-dd", "yyyy-M-d" };
                Group    dateGroup          = match.Groups["date"];
                string   dateToCheck        = null;
                if (!string.IsNullOrEmpty(dateGroup.Value))
                {
                    dateToCheck = dateGroup.ToString();
                }

                if (string.IsNullOrEmpty(dateGroup.Value))
                {
                    AddErrorWithDeprecationFallback(string.Format(
                                                        "Date field is missing. A date is required in each version section (except for '## [Unreleased]') in '{0}'. Expecting expecting ISO 8601 date format 'YYYY-MM-DD'. Update the date to one of the supported values. {1}",
                                                        changeLogPath,
                                                        ErrorDocumentation.GetLinkMessage(ErrorTypes.ChangelogDateIsNotValid)), match.ToString(), foundIndex, currentIndex);
                }
                else if (!DateTime.TryParseExact(dateToCheck,
                                                 dateFormat,
                                                 CultureInfo.InvariantCulture,
                                                 DateTimeStyles.None,
                                                 out date))
                {
                    string errorString = string.Format(
                        "Date '{0}' does not follow ISO 8601 in '{1}'. Expecting format 'YYYY-MM-DD'. Update the date to one of the supported values. {2}",
                        dateToCheck, changeLogPath,
                        ErrorDocumentation.GetLinkMessage(ErrorTypes.ChangelogDateIsNotValid));
                    if (DateTime.TryParseExact(dateToCheck,
                                               dateWarningFormats,
                                               CultureInfo.InvariantCulture,
                                               DateTimeStyles.None,
                                               out date))
                    {
                        AddWarningWithLine(errorString, match.ToString());
                    }
                    else
                    {
                        AddErrorWithDeprecationFallback(errorString, match.ToString(), foundIndex, currentIndex);
                    }
                }

                currentIndex++;
            }

            if (found == null)
            {
                var expected = string.Format("`## [{0}] - YYYY-MM-DD` or `## [{1}] - YYYY-MM-DD`", packageJsonVersion.ToString(), packageJsonVersionNoPrelease.ToString());
                AddError("No changelog entry for version `{0}` was found in '{2}'. Please add or fix a section so you have a {1} section. {3}", packageJsonVersion.ToString(), expected, changeLogPath, ErrorDocumentation.GetLinkMessage(ErrorTypes.NoChangelogEntryForVersionFound));
            }
            else if (foundIndex != 1)
            {
                AddErrorWithLine(string.Format("Found changelog entry but it was not the first entry in '{1}' (it was entry #{0}). Please rearrange your changelog with the most recent section at the top. {2}", foundIndex, changeLogPath, ErrorDocumentation.GetLinkMessage(ErrorTypes.FoundChangelogWrongPosition)), found.ToString());
            }
        }
        private void LifecycleV2VersionValidator(SemVersion packageVersionNumber, VersionTag versionTag)
        {
            if (versionTag.IsEmpty())
            {
                return;
            }

            if (versionTag.Tag == "preview")
            {
                AddError("In package.json, \"version\" cannot be tagged \"preview\" in lifecycle v2, please use \"exp\". " + ErrorDocumentation.GetLinkMessage(ErrorTypes.InvalidLifecycleV2));
                return;
            }

            if (packageVersionNumber.Major < 1)
            {
                AddError("In package.json, \"version\" cannot be tagged \"" + packageVersionNumber.Prerelease + "\" while the major version less than 1. " + ErrorDocumentation.GetLinkMessage(ErrorTypes.InvalidLifecycleV2));
                return;
            }

            if (versionTag.Tag != "exp" && versionTag.Tag != "pre")
            {
                AddError("In package.json, \"version\" must be a valid tag. \"" + versionTag.Tag + "\" is invalid, try either \"pre\" or \"exp\". " + ErrorDocumentation.GetLinkMessage(ErrorTypes.InvalidLifecycleV2));
                return;
            }

            if (versionTag.Tag != "exp" && versionTag.Feature != "")
            {
                AddError("In package.json, \"version\" must be a valid tag. Custom tag \"" + versionTag.Feature + "\" only allowed with \"exp\". " + ErrorDocumentation.GetLinkMessage(ErrorTypes.InvalidLifecycleV2));
                return;
            }

            if (versionTag.Tag == "exp" && versionTag.Feature.Length > 10)
            {
                AddError("In package.json, \"version\" must be a valid tag. Custom tag \"" + versionTag.Feature + "\" is too long, must be 10 characters long or less. " + ErrorDocumentation.GetLinkMessage(ErrorTypes.InvalidLifecycleV2));
                return;
            }

            if (versionTag.Iteration < 1)
            {
                AddError("In package.json, \"version\" must be a valid tag. Iteration is required to be 1 or greater. " + ErrorDocumentation.GetLinkMessage(ErrorTypes.InvalidLifecycleV2));
                return;
            }
        }
        private void ValidateDependenciesLifecyclePhase(Dictionary <string, string> dependencies)
        {
            // No dependencies, exit early
            if (!dependencies.Any())
            {
                return;
            }

            // Extract the current track, since otherwise we'd be potentially parsing the version
            // multiple times
            var currentTrack = PackageLifecyclePhase.GetLifecyclePhaseOrRelation(Context.ProjectPackageInfo.version, Context.ProjectPackageInfo.name, Context);

            var supportedVersions = PackageLifecyclePhase.GetPhaseSupportedVersions(currentTrack);

            // Check each dependency against supported versions
            foreach (var dependency in dependencies)
            {
                // Skip invalid dependencies from this check
                SemVersion depVersion;
                if (!SemVersion.TryParse(dependency.Value, out depVersion))
                {
                    continue;
                }

                LifecyclePhase dependencyTrack = PackageLifecyclePhase.GetLifecyclePhaseOrRelation(dependency.Value.ToLower(), dependency.Key.ToLower(), Context);
                var            depId           = Utilities.CreatePackageId(dependency.Key, dependency.Value);
                if (!supportedVersions.HasFlag(dependencyTrack))
                {
                    AddError($"Package {Context.ProjectPackageInfo.Id} depends on package {depId} which is in an invalid track for release purposes. {currentTrack} versions can only depend on {supportedVersions.ToString()} versions. {ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "package-depends-on-a-package-which-is-in-an-invalid-track-for-release-purposes")}");
                }
            }
        }
        private void CheckApiDiff(AssemblyInfo[] assemblyInfo)
        {
#if UNITY_2018_1_OR_NEWER && !UNITY_2019_1_OR_NEWER
            TestState = TestState.NotRun;
            AddInformation("Api breaking changes validation only available on Unity 2019.1 or newer.");
            return;
#else
            var diff = new ApiDiff();
            var assembliesForPackage = assemblyInfo.Where(a => !validationAssemblyInformation.IsTestAssembly(a)).ToArray();
            if (Context.PreviousPackageBinaryDirectory == null)
            {
                TestState = TestState.NotRun;
                AddInformation("Previous package binaries must be present on artifactory to do API diff.");
                return;
            }

            var oldAssemblyPaths = Directory.GetFiles(Context.PreviousPackageBinaryDirectory, "*.dll");

            //Build diff
            foreach (var info in assembliesForPackage)
            {
                var assemblyDefinition = info.assemblyDefinition;
                var oldAssemblyPath    = oldAssemblyPaths.FirstOrDefault(p => Path.GetFileNameWithoutExtension(p) == assemblyDefinition.name);

                if (info.assembly != null)
                {
                    var extraSearchFolder    = Path.GetDirectoryName(typeof(System.ObsoleteAttribute).Assembly.Location);
                    var assemblySearchFolder = new[]
                    {
                        extraSearchFolder,                                                              // System assemblies folder
                        Path.Combine(EditorApplication.applicationContentsPath, "Managed"),             // Main Unity assemblies folder.
                        Path.Combine(EditorApplication.applicationContentsPath, "Managed/UnityEngine"), // Module assemblies folder.
                        Path.GetDirectoryName(info.assembly.outputPath),                                // TODO: This is not correct. We need to keep all dependencies for the previous binaries. For now, use the same folder as the current version when resolving dependencies.
                        Context.ProjectPackageInfo.path                                                 // make sure to add the package folder as well, because it may contain .dll files
                    };

                    const string logsDirectory = "Logs";
                    if (!Directory.Exists(logsDirectory))
                    {
                        Directory.CreateDirectory(logsDirectory);
                    }

                    File.WriteAllText($"{logsDirectory}/ApiValidationParameters.txt", $"previous: {oldAssemblyPath}\ncurrent: {info.assembly.outputPath}\nsearch path: {string.Join("\n", assemblySearchFolder)}");
                    var apiChangesAssemblyInfo = new APIChangesCollector.AssemblyInfo()
                    {
                        BaseAssemblyPath = oldAssemblyPath,
                        BaseAssemblyExtraSearchFolders = assemblySearchFolder,
                        CurrentAssemblyPath            = info.assembly.outputPath,
                        CurrentExtraSearchFolders      = assemblySearchFolder
                    };

                    List <IAPIChange> entityChanges;
                    try
                    {
                        entityChanges = APIChangesCollector.Collect(apiChangesAssemblyInfo)
                                        .SelectMany(c => c.Changes).ToList();
                    }
                    catch (AssemblyResolutionException exception)
                    {
                        if (exception.AssemblyReference.Name == "UnityEditor.CoreModule" ||
                            exception.AssemblyReference.Name == "UnityEngine.CoreModule")
                        {
                            AddError(
                                "Failed comparing against assemblies of previously promoted version of package. \n" +
                                "This is most likely because the assemblies that were compared against were built with a different version of Unity. \n" +
                                "If you are certain that there are no API changes warranting bumping the package version then you can add an exception for this error:\n" +
                                ErrorDocumentation.GetLinkMessage("validation_exceptions.html", ""));
                            AddInformation($"APIChangesCollector.Collect threw exception:\n{exception}");
                            return;
                        }

                        throw;
                    }

                    var assemblyChange = new AssemblyChange(info.assembly.name)
                    {
                        additions = entityChanges.Where(c => c.IsAdd()).Select(c => c.ToString()).ToList(),
                        // Among all attribute changes, only the Obsolete attribute should be considered a breaking change
                        breakingChanges = entityChanges.Where(c => !c.IsAdd() && !((c.GetType()).Equals(typeof(AttributeChange)))).Select(c => c.ToString()).ToList()
                    };

                    if (entityChanges.Count > 0)
                    {
                        diff.assemblyChanges.Add(assemblyChange);
                    }
                }

                if (oldAssemblyPath == null)
                {
                    diff.newAssemblies.Add(assemblyDefinition.name);
                }
            }

            foreach (var oldAssemblyPath in oldAssemblyPaths)
            {
                var oldAssemblyName = Path.GetFileNameWithoutExtension(oldAssemblyPath);
                if (assembliesForPackage.All(a => a.assemblyDefinition.name != oldAssemblyName))
                {
                    diff.missingAssemblies.Add(oldAssemblyName);
                }
            }

            //separate changes
            diff.additions            = diff.assemblyChanges.Sum(v => v.additions.Count);
            diff.removedAssemblyCount = diff.missingAssemblies.Count;
            diff.breakingChanges      = diff.assemblyChanges.Sum(v => v.breakingChanges.Count);

            AddInformation("Tested against version {0}", Context.PreviousPackageInfo.Id);
            AddInformation("API Diff - Breaking changes: {0} Additions: {1} Missing Assemblies: {2}",
                           diff.breakingChanges,
                           diff.additions,
                           diff.removedAssemblyCount);

            if (diff.breakingChanges > 0 || diff.additions > 0)
            {
                TestOutput.AddRange(diff.assemblyChanges.Select(c => new ValidationTestOutput()
                {
                    Type = TestOutputType.Information, Output = JsonUtility.ToJson(c, true)
                }));
            }

            string json = JsonUtility.ToJson(diff, true);
            Directory.CreateDirectory(ValidationSuiteReport.ResultsPath);
            File.WriteAllText(Path.Combine(ValidationSuiteReport.ResultsPath, "ApiValidationReport.json"), json);

            //Figure out type of version change (patch, minor, major)
            //Error if changes are not allowed
            var changeType = Context.VersionChangeType;

            if (changeType == VersionChangeType.Unknown)
            {
                return;
            }

            if (Context.ProjectPackageInfo.LifecyclePhase == LifecyclePhase.Preview || Context.ProjectPackageInfo.LifecyclePhase == LifecyclePhase.Experimental)
            {
                ExperimentalPackageValidateApiDiffs(diff, changeType);
            }
            else
            {
                ReleasePackageValidateApiDiffs(diff, changeType);
            }
#endif
        }
        private void ValidateVersion(ManifestData manifestData, Action <SemVersion, VersionTag> lifecycleVersionValidator)
        {
            // Check package version, make sure it's a valid SemVer string.
            SemVersion packageVersionNumber;

            if (!SemVersion.TryParse(manifestData.version, out packageVersionNumber))
            {
                AddError("In package.json, \"version\" needs to be a valid \"Semver\". {0}", ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "version-needs-to-be-a-valid-semver"));
                return;
            }

            VersionTag versionTag;

            try
            {
                versionTag = VersionTag.Parse(packageVersionNumber.Prerelease);
            }
            catch (ArgumentException e)
            {
                AddError("In package.json, \"version\" doesn't follow our lifecycle rules. {0}. {1}", e.Message, ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "version-is-invalid-tag-must-follow-lifecycle-rules"));
                return;
            }

            lifecycleVersionValidator(packageVersionNumber, versionTag);
            ValidateVersionAbilityToPromote(packageVersionNumber, versionTag, manifestData);
        }
        private void PreReleaseChecks(ManifestData currentManifest)
        {
            string errorMsg = String.Empty;

            if (Context.AllVersions != null)
            {
                SemVersion pkgVersion = SemVersion.Parse(currentManifest.version);
                // Get all versions that match x.y.z (so, any exp, preview, prerelease, etc).
                List <SemVersion> relatedVersions = pkgVersion.GetRelatedVersions(Context.AllVersions);

                // Get only the related previous versions
                SemVersion prevVersion = relatedVersions.Where(v => v <= pkgVersion).ToList().LastOrDefault();

                if (prevVersion != null)
                {
                    if (!PackageLifecyclePhase.IsPreReleaseVersion(prevVersion, VersionTag.Parse(prevVersion.Prerelease)))
                    {
                        errorMsg = string.Format(
                            "The previous version of this package ({0}) is not a Pre-Release. By Lifecycle V2 rules, a Pre-Release package can only be promoted automatically to production when the previous version is also a Pre-Release version. {1}",
                            prevVersion.ToString(),
                            ErrorDocumentation.GetLinkMessage(k_DocsFilePath,
                                                              "previous-version-of-this-package-is-not-a-pre-release-version"));
                    }
                    else
                    {
                        SemVersion lastPreVersion = relatedVersions.Where(v =>
                        {
                            VersionTag t = VersionTag.Parse(v.Prerelease);
                            return(PackageLifecyclePhase.IsPreReleaseVersion(v, t));
                        }).ToList().LastOrDefault();

                        if (lastPreVersion != null)
                        {
                            VersionTag lastTag = VersionTag.Parse(lastPreVersion.Prerelease);
                            VersionTag pkgTag  = VersionTag.Parse(pkgVersion.Prerelease);
                            if (pkgTag.Iteration <= lastTag.Iteration)
                            {
                                errorMsg = string.Format(
                                    "This package iteration ({0}) must be higher than the highest published iteration ({1}). Please update your package version to {2} {3}",
                                    pkgTag.Iteration,
                                    lastTag.Iteration,
                                    string.Format("{0}-{1}.{2}", pkgVersion.VersionOnly(), pkgTag.Tag,
                                                  lastTag.Iteration + 1),
                                    ErrorDocumentation.GetLinkMessage(k_DocsFilePath,
                                                                      "this-package-iteration-(x)-must-be-higher-than-the-highest-published-iteration-(y)"));
                            }
                        }
                    }
                }
                else
                {
                    errorMsg = string.Format(
                        "There is no previous Pre-Release version of this package available. By Lifecycle V2 rules, the first Pre-Release iteration of a new version needs to be approved and promoted by Release Management. Please contact Release Management to promote your package. {0}",
                        ErrorDocumentation.GetLinkMessage(k_DocsFilePath,
                                                          "previous-version-of-this-package-is-not-a-pre-release-version"));
                }
            }

            if (errorMsg != String.Empty)
            {
                AddPromotionConditionalError(errorMsg);
            }
        }
        /**
         * Can't promote if it's a release or RC
         * Can promote -preview, -pre (unless it is the first one, but that is verified somewhere else) and -exp
         * Error in Promotion
         * Only warn when in CI
         */
        private void ValidateVersionAbilityToPromote(SemVersion packageVersionNumber, VersionTag versionTag, ManifestData manifestData)
        {
            // Make this check only in promotion, to avoid network calls
            if (Context.PackageVersionExistsOnProduction)
            {
                AddPromotionConditionalError("Version " + Context.ProjectPackageInfo.version + " of this package already exists in production.");
            }

            var message = String.Empty;

            if (PackageLifecyclePhase.IsReleasedVersion(packageVersionNumber, versionTag) ||
                PackageLifecyclePhase.IsRCForThisEditor(manifestData.name, Context))
            {
                message = $"Automated promotion of Release or Release Candidate packages is not allowed. Release Management are the only ones that can promote Release and Release Candidate packages to production, if you need this to happen, please go to #devs-pkg-promotion. {ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "a-release-package-must-be-manually-promoted-by-release-management")}";
            }
            else
            {
                // We send a message if this is the first version of the package being promoted
                if (!Context.PackageExistsOnProduction)
                {
                    message = $"{Context.PublishPackageInfo.name} has never been promoted to production before. Please contact Release Management through slack in #devs-pkg-promotion to promote the first version of your package before trying to use this automated pipeline. {ErrorDocumentation.GetLinkMessage(k_DocsFilePath, "the-very-first-version-of-a-package-must-be-promoted-by-release-management")}";
                }
            }

            if (message != String.Empty)
            {
                AddPromotionConditionalError(message);
            }
        }