/// <summary>
        ///
        /// </summary>
        /// <param name="packageName">eg: com.unity.package-manager-ui</param>
        /// <param name="shortVersionId">eg: [email protected] (note: not @1.2.0)</param>
        ///<param name="siteFolder">Output folder where the doc site should be created.</param>
        public virtual void Build(string packageName, string shortVersionId, string siteFolder = null)
        {
            if (string.IsNullOrEmpty(siteFolder))
            {
                siteFolder = GetPackageSiteFolder(shortVersionId);
            }

            string packageFolder = Path.GetFullPath(string.Format("packages/{0}", packageName));
            string buildFolder   = GetPackageBuildFolder(shortVersionId);
            string docfxJson     = Path.Combine(buildFolder, "docfx.json");
            string logoSource    = Path.Combine(buildFolder, "logo.svg");
            string logoTarget    = Path.Combine(siteFolder, "logo.svg");
            string manualSource  = Path.Combine(buildFolder, "manual");

            if (!Directory.Exists(MonoRootPath))
            {
                ZipUtils.Unzip(MonoZip, MonoRootPath);
            }

            //
            // Setup Mono for Mac/Linux
            if (Application.platform != RuntimePlatform.WindowsEditor)
            {
                var monoPath = Path.Combine(DocumentationRoot, "mono");
                if (!Directory.Exists(monoPath))
                {
                    FileUtil.CopyFileOrDirectory(MonoRootPath, monoPath);
                    SetFolderPermission(monoPath, "777");
                }

                MonoPath = Path.Combine(monoPath, "bin/mono-sgen64");
            }

            //
            // Set target folders to initial condition
            Directory.CreateDirectory(buildFolder);

            // Clear build folder
            FileUtil.DeleteFileOrDirectory(buildFolder);

            // Clear site folder prior to building
            FileUtil.DeleteFileOrDirectory(siteFolder);

            //
            // Prepare build folder
            string packageCloneFolder = Path.Combine(buildFolder, "package");

            FileUtil.CopyFileOrDirectory(DocFxTemplateRoot, buildFolder);
            FileUtil.CopyFileOrDirectory(packageFolder, packageCloneFolder);

            // Remove ignored folders in the generated doc
            foreach (string dirPath in Directory.GetDirectories(packageCloneFolder, ".*", SearchOption.AllDirectories))
            {
                FileUtil.DeleteFileOrDirectory(dirPath);
            }

            foreach (string dirPath in Directory.GetDirectories(packageCloneFolder, "*~", SearchOption.AllDirectories))
            {
                FileUtil.DeleteFileOrDirectory(dirPath);
            }

            // When used from the cache (which is read-only), it is important to set the folder properties to write.
            SetFolderPermission(buildFolder);

            // Prepare feature doc for docfx
            string packageDocs      = Path.Combine(packageFolder, "Documentation");
            string packageDocsTilde = Path.Combine(packageFolder, "Documentation~");
            string packageDocsDot   = Path.Combine(packageFolder, ".Documentation");

            if (Directory.Exists(packageDocsTilde))
            {
                packageDocs = packageDocsTilde;
            }
            else if (Directory.Exists(packageDocsDot))
            {
                packageDocs = packageDocsDot;
            }

            var sourceManualFiles = Directory.GetFiles(packageDocs, "*.md", SearchOption.AllDirectories);

            if (Directory.Exists(packageDocs) && sourceManualFiles.Any())
            {
                DirectoryCopy(packageDocs, manualSource);
            }
            else
            {
                Debug.LogWarning("Package Documentation Build: Feature Documentation does not exist. An empty placeholder will be used instead");
                string manualFile = Path.Combine(manualSource, "index.md");
                File.WriteAllText(manualFile, "Information\n-----------\n<span style=\"color:red\">There is currently no documentation for this package.</span>");
            }

            var filterFilesource = Path.Combine(manualSource, "filter.yml");

            if (File.Exists(filterFilesource))
            {
                SetFolderPermission(manualSource, "777");
                var filterTarget = Path.Combine(buildFolder, "filter.yml");
                File.Delete(filterTarget);
                File.Copy(filterFilesource, filterTarget);
                File.Delete(filterFilesource);
            }

            var projectMetadataFileSource = Path.Combine(manualSource, "projectMetadata.json");

            if (File.Exists(projectMetadataFileSource))
            {
                SetFolderPermission(manualSource, "777");
                var projectMetadataFileTarget = Path.Combine(buildFolder, "projectMetadata.json");
                File.Delete(projectMetadataFileTarget);
                File.Copy(projectMetadataFileSource, projectMetadataFileTarget);
                File.Delete(projectMetadataFileSource);
            }

            // Setup the default title - replace all appearances of `DEFAULT_APP_TITLE` with the real default `PACKAGE DISPLAY NAME | VERSION`
            SetupDefaultTitle(packageFolder, buildFolder);

            // Generate table of content for manual
            var docfxToc = Path.Combine(manualSource, "toc.md");

            if (File.Exists(docfxToc))
            {
                Debug.LogError("Your table of content file is named 'toc.md' and should be named 'TableOfContents.md'. Please rename it.");
            }

            var docWorksToC = Path.Combine(manualSource, "TableOfContents.md");

            if (File.Exists(docWorksToC))
            {
                // DocWorks ToC support
                // Index.md is necessary for DocFx for now since it's embedded in the template.
                if (!File.Exists(Path.Combine(manualSource, "index.md")))
                {
                    Debug.LogError("Package Documentation generation error. Need an index.md when using a toc.md. Resulting site will not work.");
                    return;
                }

                docfxJson = Path.Combine(buildFolder, "docfx_toc_enabled.json");    // Force ToC to be enabled on manual page

                // Convert DocWorks style ToC to DocFx ToC format
                ConvertDocWorksToC(docWorksToC, Path.Combine(buildFolder, "manual/toc.md"));
            }
            else
            {
                //
                // Simple manual support
                var manualFiles = Directory.GetFiles(manualSource, "*.md", SearchOption.AllDirectories);
                var manualIndexCandidateFile = manualFiles.First();
                var manualIndexFile          = Path.Combine(manualSource, "index.md");
                if (!File.Exists(manualIndexFile))
                {
                    FileUtil.MoveFileOrDirectory(manualIndexCandidateFile, manualIndexFile);
                }

                // Landing page for feature doc is alway index.md since that's required to make sure
                // that the docfx home page always redirects to this specific url.
                string ymlToc    = "- name: Introduction\n  href: index.md\n";
                string manualToc = Path.Combine(manualSource, "toc.yml");
                File.WriteAllText(manualToc, ymlToc);
            }

            // Add License section
            string ymlLicenseToc = string.Format("- name: {0}\n  href: LICENSE.md\n", "License");

            // Will create an index.html that can always be linked to that will redirect either to third party license (if any) or the companion license
            string licenseIndexFile = Path.Combine(buildFolder, "license/index.md");
            string licenseIndexName = "LICENSE";

            var licenseFile = Path.Combine(packageFolder, "LICENSE.md");

            if (!File.Exists(licenseFile))
            {
                licenseFile = Path.Combine(packageFolder, "LICENSE");
            }

            if (File.Exists(licenseFile))
            {
                File.Copy(licenseFile, Path.Combine(buildFolder, "license/LICENSE.md"), true);
            }
            else
            {
                throw new Exception("No valid LICENSE.md file found at the root of the package");
            }

            var licenseThirdPartyFile       = Path.Combine(packageFolder, "Third Party Notices.md");
            var licenseThirdPartyFileTarget = Path.Combine(buildFolder, "license/Third Party Notices.md");

            if (File.Exists(licenseThirdPartyFile))
            {
                File.Copy(licenseThirdPartyFile, licenseThirdPartyFileTarget, true);
                licenseIndexName = "Third%20Party%20Notices";
                ymlLicenseToc    = "- name: Third Party Notices\n  href: Third Party Notices.md\n" + ymlLicenseToc;
            }

            File.WriteAllText(licenseIndexFile, string.Format("<script>window.location.replace('{0}.html')</script>", licenseIndexName));

            string licenseToc = Path.Combine(buildFolder, "license/toc.yml");

            File.WriteAllText(licenseToc, ymlLicenseToc);

            var     versionToken = shortVersionId.Split('@')[1];
            Version version;

            Version.TryParse(versionToken, out version);

            if (packageName == "com.unity.tiny" && version < new Version(0, 15))
            {
                File.Copy(Path.Combine(buildFolder, "toc_tiny.yml"), Path.Combine(buildFolder, "toc.yml"), true);
                docfxJson = Path.Combine(buildFolder, "docfx_toc_tiny.json");
                CreateTinyRuntimeDoc(buildFolder);
            }

            // Use the changelog, otherwise keep the empty default changelog.
            var changelogFile = Path.Combine(packageFolder, "CHANGELOG.md");

            if (File.Exists(changelogFile))
            {
                File.Copy(changelogFile, Path.Combine(buildFolder, "changelog/CHANGELOG.md"), true);
            }

            // Generate solution file
            // This is necessary to have proper cross linking across namespaces.
            // Otherwise, the links in NamespaceA.ClassA.MemberA to NamespaceB.ClassB will not be clickable in the generated doc
            // It also allows having preprocessor defines for code generation
            var constantsDefines   = "PACKAGE_DOCS_GENERATION;";
            var docToolsConfigFile = Path.Combine(packageDocs, "config.json");
            var docToolsConfig     = new DocToolsBuildConfig();

            if (File.Exists(docToolsConfigFile))
            {
                docToolsConfig = JsonUtility.FromJson <DocToolsBuildConfig>(File.ReadAllText(docToolsConfigFile));
            }

            var configInManual = Path.Combine(manualSource, "config.json");

            if (File.Exists(configInManual))
            {
                File.Delete(configInManual);
            }

            docToolsConfig.DefineConstants = constantsDefines + docToolsConfig.DefineConstants;

            var solutionPrefix = "<Project ToolsVersion=\"4.0\" DefaultTargets=\"FullPublish\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\"><PropertyGroup><DefineConstants>" + docToolsConfig.DefineConstants + "</DefineConstants></PropertyGroup><ItemGroup>";
            var solutionSuffix = "</ItemGroup></Project>";
            var solutionItems  = "";

            foreach (string cs in Directory.GetFiles(packageCloneFolder, "*.cs", SearchOption.AllDirectories))
            {
                solutionItems += string.Format("<Compile Include=\"{0}\"/>", cs);
            }

            var solution = string.Format("{0}\n{1}\n{2}", solutionPrefix, solutionItems, solutionSuffix);

            File.WriteAllText(Path.Combine(packageCloneFolder, "solution.csproj"), solution);

            //
            // Build the doc
            string arguments = string.Format("\"{0}\"", docfxJson);
            var    success   = RunDocFx(arguments);

            // Copy svg logo
            if (success)
            {
                FileUtil.MoveFileOrDirectory(Path.Combine(buildFolder, "_site"), siteFolder);

                File.Copy(logoSource, logoTarget, true);

                // Clear build folder (unless in internal mode for easier debugging)
                if (!Unsupported.IsDeveloperMode())
                {
                    FileUtil.DeleteFileOrDirectory(buildFolder);
                }
            }
        }
        private bool RunDocFx(string docFxArguments, bool serveMode = false)
        {
            //
            // Build using docfx
            Process docfxProcess = new Process();
            var     startInfo    = docfxProcess.StartInfo;

            if (serveMode)
            {
                serveProcess = docfxProcess;
            }

            if (!Directory.Exists(DocFxRoot))
            {
                ZipUtils.Unzip(DocFxZip, DocFxRoot);
            }

            var    applicationExec = MonoPath;
            string arguments       = string.Format("\"{0}\" {1}", DocFxExecutable, docFxArguments);

            if (Application.platform == RuntimePlatform.WindowsEditor)
            {
                applicationExec = DocFxExecutable;
                arguments       = docFxArguments;
            }

            string processLog = "";
            var    success    = true;

            try
            {
                startInfo.UseShellExecute        = false;
                startInfo.FileName               = applicationExec;
                startInfo.Arguments              = arguments;
                startInfo.WorkingDirectory       = DocFxTemplateRoot;
                startInfo.CreateNoWindow         = true;
                startInfo.RedirectStandardOutput = true;
                startInfo.RedirectStandardError  = true;
                if (serveMode)
                {
                    startInfo.RedirectStandardInput = true;
                }

                // Set our event handler to asynchronously read the sort output.
                docfxProcess.OutputDataReceived += DocFxOutputHandler;

                var startMessage = string.Format("DocFx Command line ran: \"{0}\" {1}", applicationExec, arguments);
                if (GlobalSettings.Verbose)
                {
                    if (serveMode)
                    {
                        serveLog += "Doc Fx Serve Output: " + startMessage + "\n";
                    }
                    else
                    {
                        Debug.Log(startMessage);
                    }
                }

                docfxProcess.Start();

                docfxProcess.BeginOutputReadLine();

                // To avoid deadlocks, always read the output stream first and then wait.
                // Also don't read to end both standard outputs.
                processLog += docfxProcess.StandardError.ReadToEnd();

                if (!serveMode)
                {
                    docfxProcess.WaitForExit();
                }
            }
            catch (ThreadAbortException) {}    // Don't consider this an error.
            catch (Exception e)
            {
                success = false;
                Debug.LogError(e.Message);
            }

            if (docfxProcess.HasExited && docfxProcess.ExitCode != 0)
            {
                success = false;
            }

            var message = string.Format("DocFx output:\n{0}", processLog);

            if (GlobalSettings.Verbose && !string.IsNullOrEmpty(processLog))
            {
                if (serveMode)
                {
                    serveLog += "Doc Fx Serve Output: " + message + "\n";
                }
                else
                {
                    Debug.Log(message);
                }
            }

            stopServeLogWatch = true;

            if (!success)
            {
                Debug.LogError(message);
            }

            return(success);
        }