diff --git a/azure-pipelines-v3.yml b/azure-pipelines-v3.yml
index cc3349e3180..f874a5da20e 100644
--- a/azure-pipelines-v3.yml
+++ b/azure-pipelines-v3.yml
@@ -211,8 +211,8 @@ parameters:
params: https://github.com/MicrosoftDocs/sql-docs-pr --profile --timeout 120 --regression-rules
docs:
params: https://github.com/dotnet/docs --timeout 60 --regression-rules
- # learn-pr:
- # params: https://github.com/MicrosoftDocs/learn-pr --timeout 105 --no-dry-sync --regression-rules
+ learn-pr:
+ params: https://github.com/MicrosoftDocs/learn-pr --timeout 105 --no-dry-sync --regression-rules
windowsserverdocs-pr:
params: https://github.com/MicrosoftDocs/windowsserverdocs-pr --timeout 45 --regression-rules
VBA-Docs:
@@ -231,8 +231,8 @@ parameters:
params: https://github.com/Azure/azure-docs-rest-apis --timeout 130 --error-level Warning
powerapps-docs-web-api-ref-pr:
params: https://github.com/MicrosoftDocs/powerapps-docs-web-api-ref-pr --timeout 25
- mc-docs-pr:
- params: https://github.com/MicrosoftDocs/mc-docs-pr --timeout 70
+ # mc-docs-pr:
+ # params: https://github.com/MicrosoftDocs/mc-docs-pr --timeout 70
dynamics365smb-devitpro:
params: https://github.com/MicrosoftDocs/dynamics365smb-devitpro --timeout 15
test:
diff --git a/docs/specs/markdown.yml b/docs/specs/markdown.yml
index b26e6c32574..c06a3850813 100644
--- a/docs/specs/markdown.yml
+++ b/docs/specs/markdown.yml
@@ -857,3 +857,15 @@ outputs:
"conceptual": "
Check Find for more details.\nThis method can be ran on Windows, and you can also used it in Azure SDK.
\n",
"no-loc": ["method", "SDK"]
}
+---
+# remove domain for alternative hostname for absolute links
+inputs:
+ docfx.yml: |
+ alternativeHostname: new-docs.microsoft.com
+ docs/a.md: |
+ [A link](https://new-docs.microsoft.com/en-us/azure)
+outputs:
+ docs/a.json: |
+ {
+ "conceptual": "A link
",
+ }
diff --git a/docs/specs/ops.yml b/docs/specs/ops.yml
index 130a2aac359..540d1893c33 100644
--- a/docs/specs/ops.yml
+++ b/docs/specs/ops.yml
@@ -191,7 +191,7 @@ outputs:
"files": [
{
"url": "/admin/integrate-with-microsoft-teams",
- "redirect_url": "basics/teams-integration",
+ "redirect_url": "/admin/basics/teams-integration",
"document_id": "b1c6c9d3-40a8-a21d-98e8-a3cbf54eaeb4",
"document_version_independent_id": "b1c6c9d3-40a8-a21d-98e8-a3cbf54eaeb4",
"canonical_url": "https://docs.com/en-us/admin/integrate-with-microsoft-teams"
diff --git a/docs/specs/redirection.yml b/docs/specs/redirection.yml
index 36fb1a727c3..7eee501dd05 100644
--- a/docs/specs/redirection.yml
+++ b/docs/specs/redirection.yml
@@ -976,3 +976,180 @@ outputs:
.errors.log: |
{"message_severity":"warning","code":"path-invalid","message":"Path /*invalid.md contains invalid chars '<', '>', '*'."}
{"message_severity":"warning","code":"missing-attribute","message":"Missing required attribute: '(source_path or source_path_from_root) or redirect_url'."}
+---
+# Remove redirect_url hostname when it is the same as hostname or alternativeHostName in despite of redirect_document_id
+repos:
+ https://docs.com/ops/redirect:
+ - files:
+ redirections.json: |
+ {
+ "redirections": [
+ {
+ "source_path": "original.md",
+ "redirect_url": "https://docs.com/folder/a",
+ "redirect_document_id": "true"
+ },
+ {
+ "source_path": "original2.md",
+ "redirect_url": "https://docs.com/folder/b",
+ "redirect_document_id": "false"
+ },
+ {
+ "source_path": "original3.md",
+ "redirect_url": "https://new-docs.com/folder/c",
+ "redirect_document_id": "true"
+ },
+ {
+ "source_path": "original4.md",
+ "redirect_url": "https://new-docs.com/folder/d",
+ "redirect_document_id": "false"
+ },
+ {
+ "source_path": "original5.md",
+ "redirect_url": "https://docs-diff.com/folder/c",
+ "redirect_document_id": "true"
+ },
+ {
+ "source_path": "original6.md",
+ "redirect_url": "https://docs-diff.com/folder/d",
+ "redirect_document_id": "false"
+ },
+ ]
+ }
+ docfx.yml: |
+ hostname: docs.com
+ alternativeHostName: new-docs.com
+ name: docset
+ product: product
+ folder/index.md:
+outputs:
+ folder/index.json:
+ .publish.json: |
+ {
+ "name": "docset",
+ "product": "product",
+ "base_path": "/",
+ "files": [
+ {
+ "url": "/original",
+ "redirect_url": "/folder/a",
+ "document_id": "6172869a-1d42-80e4-5d0c-707167a0d4c1",
+ "document_version_independent_id": "6172869a-1d42-80e4-5d0c-707167a0d4c1",
+ "canonical_url": "https://docs.com/en-us/original"
+ },
+ {
+ "url": "/original2",
+ "redirect_url": "/folder/b",
+ "document_id": "0e929cdb-46ae-915a-031f-4c6f8e8f6a1a",
+ "document_version_independent_id": "0e929cdb-46ae-915a-031f-4c6f8e8f6a1a",
+ "canonical_url": "https://docs.com/en-us/original2"
+ },
+ {
+ "url": "/original3",
+ "redirect_url": "/folder/c",
+ "document_id": "01bf1826-aa28-49c6-d258-4c3837762c95",
+ "document_version_independent_id": "01bf1826-aa28-49c6-d258-4c3837762c95",
+ "canonical_url": "https://docs.com/en-us/original3"
+ },
+ {
+ "url": "/original4",
+ "redirect_url": "/folder/d",
+ "document_id": "168b8137-5da2-ede9-224a-b295bb82ee6a",
+ "document_version_independent_id": "168b8137-5da2-ede9-224a-b295bb82ee6a",
+ "canonical_url": "https://docs.com/en-us/original4"
+ },
+ {
+ "url": "/original5",
+ "redirect_url": "https://docs-diff.com/folder/c",
+ "document_id": "5269a2f9-8e29-8fed-7da3-4b08cbcb5972",
+ "document_version_independent_id": "5269a2f9-8e29-8fed-7da3-4b08cbcb5972",
+ "canonical_url": "https://docs.com/en-us/original5"
+ },
+ {
+ "url": "/original6",
+ "redirect_url": "https://docs-diff.com/folder/d",
+ "document_id": "3c570421-59fc-1dc7-d86a-43dbdf5d391b",
+ "document_version_independent_id": "3c570421-59fc-1dc7-d86a-43dbdf5d391b",
+ "canonical_url": "https://docs.com/en-us/original6"
+ },
+ {
+ "url": "/folder/",
+ "path": "folder/index.json",
+ "source_path": "folder/index.md",
+ "canonical_url": "https://docs.com/en-us/folder/",
+ "document_id": "87407f22-7c8e-317e-430d-96980c2fd1a6",
+ "document_version_independent_id": "87407f22-7c8e-317e-430d-96980c2fd1a6",
+ "site_name": "Docs",
+ "depot_name": "product.docset",
+ }
+ ]
+ }
+ .errors.log: |
+ {"message_severity":"suggestion","code":"redirect-url-invalid","message":"Can't redirect document ID for redirected file 'original.md' because redirect URL 'https://docs.com/folder/a' is invalid or is in a different docset. Specify a redirect_url in the same docset, or set redirect_document_id to false in .openpublishing.redirection.json.","file":"redirections.json"}
+ {"message_severity":"suggestion","code":"redirect-url-invalid","message":"Can't redirect document ID for redirected file 'original3.md' because redirect URL 'https://new-docs.com/folder/c' is invalid or is in a different docset. Specify a redirect_url in the same docset, or set redirect_document_id to false in .openpublishing.redirection.json.","file":"redirections.json"}
+ {"message_severity":"suggestion","code":"redirect-url-invalid","message":"Can't redirect document ID for redirected file 'original5.md' because redirect URL 'https://docs-diff.com/folder/c' is invalid or is in a different docset. Specify a redirect_url in the same docset, or set redirect_document_id to false in .openpublishing.redirection.json.","file":"redirections.json"}
+---
+# convert relative redirect url to absolute path in despite of redirect_document_id config
+repos:
+ https://docs.com/ops/redirect:
+ - files:
+ redirections.json: |
+ {
+ "redirections": [
+ {
+ "source_path": "original.md",
+ "redirect_url": "folder/a",
+ "redirect_document_id": "true"
+ },
+ {
+ "source_path": "original2.md",
+ "redirect_url": "folder/b",
+ "redirect_document_id": "false"
+ }
+ ]
+ }
+ docfx.yml: |
+ hostname: docs.com
+ name: docset
+ product: product
+ folder/index.md:
+outputs:
+ folder/index.json:
+ .publish.json: |
+ {
+ "name": "docset",
+ "product": "product",
+ "base_path": "/",
+ "files": [
+ {
+ "url": "/original",
+ "locale": "en-us",
+ "redirect_url": "/folder/a",
+ "document_id": "6172869a-1d42-80e4-5d0c-707167a0d4c1",
+ "document_version_independent_id": "6172869a-1d42-80e4-5d0c-707167a0d4c1",
+ "canonical_url": "https://docs.com/en-us/original"
+ },
+ {
+ "url": "/original2",
+ "locale": "en-us",
+ "redirect_url": "/folder/b",
+ "document_id": "0e929cdb-46ae-915a-031f-4c6f8e8f6a1a",
+ "document_version_independent_id": "0e929cdb-46ae-915a-031f-4c6f8e8f6a1a",
+ "canonical_url": "https://docs.com/en-us/original2"
+ },
+ {
+ "url": "/folder/",
+ "path": "folder/index.json",
+ "source_path": "folder/index.md",
+ "locale": "en-us",
+ "canonical_url": "https://docs.com/en-us/folder/",
+ "document_id": "87407f22-7c8e-317e-430d-96980c2fd1a6",
+ "document_version_independent_id": "87407f22-7c8e-317e-430d-96980c2fd1a6",
+ "site_name": "Docs",
+ "depot_name": "product.docset",
+ }
+ ]
+ }
+ .errors.log: |
+ {"message_severity":"suggestion","code":"redirect-url-invalid","message":"Can't redirect document ID for redirected file 'original.md' because redirect URL 'folder/a' is invalid or is in a different docset. Specify a redirect_url in the same docset, or set redirect_document_id to false in .openpublishing.redirection.json.","file":"redirections.json"}
+---
diff --git a/docs/specs/xref.yml b/docs/specs/xref.yml
index 9f9ba034f50..4c6ae897900 100644
--- a/docs/specs/xref.yml
+++ b/docs/specs/xref.yml
@@ -185,31 +185,6 @@ outputs:
}
}
---
-# Remove host from review site
-# This can be removed when xref related repo migrated to v3
-repos:
- https://docs.com/test-uid-conceptual#live:
- - files:
- docfx.yml: |
- hostName: docs.microsoft.com
- basePath: /base_path
- xref:
- - 1.xrefmap.json
- docs/a.md: Link to @System.String
- 1.xrefmap.json: |
- {
- "references":[{
- "uid": "System.String",
- "name": "String",
- "fullName": "System.String",
- "href": "https://review.docs.microsoft.com/dotnet/api/system.string",
- "nameWithType": "System.String"
- }]
- }
-outputs:
- base_path/docs/a.json: |
- { "conceptual": "Link to String
\n"}
----
# The same uid in internal and external
# should resolve it from the internal one
inputs:
@@ -2198,3 +2173,293 @@ outputs:
{"message_severity":"warning","code":"xref-not-found","message":"Cross reference not found: 'a'.","file":"c.md"}
c.json: |
{ "conceptual": "Link to <xref:a>
" }
+---
+# [alternativeHostName][xrefmap][before] output xrefmap with main hostname
+# as alternative hostname may be not available
+repos:
+ https://docs.com/test-uid-conceptual#live:
+ - files:
+ docfx.yml: |
+ hostName: docs.microsoft.com
+ basePath: /base_path
+ alternativeHostName: new-docs.microsoft.com
+ docs/a.md: |
+ ---
+ title: Title from yaml header a
+ uid: a
+ ---
+ docs/b.md: |
+ ---
+ title: Title from yaml header b
+ uid: b
+ ---
+ docs/c.md: Link to @a
+outputs:
+ base_path/docs/c.json: |
+ {"conceptual":"Link to Title from yaml header a
\n"}
+ base_path/docs/a.json:
+ base_path/docs/b.json:
+ .xrefmap.json: |
+ {
+ "references":[
+ {
+ "uid": "a",
+ "href": "https://docs.microsoft.com/base_path/docs/a",
+ "name": "Title from yaml header a"
+ },
+ {
+ "uid": "b",
+ "href": "https://docs.microsoft.com/base_path/docs/b",
+ "name": "Title from yaml header b"
+ }
+ ]
+ }
+---
+# [alternativeHostName][xrefmap][before] output xrefmap with xrefhostName if xrefhostName presents
+# as alternative hostname may be not available
+BuildEnvironment: Prod
+repos:
+ https://docs.com/test-uid-conceptual#main:
+ - files:
+ docfx.yml: |
+ hostName: docs.microsoft.com
+ xrefhostName: review.docs.microsoft.com
+ basePath: /base_path
+ alternativeHostName: new-docs.microsoft.com
+ docs/a.md: |
+ ---
+ title: Title from yaml header a
+ uid: a
+ ---
+ docs/b.md: |
+ ---
+ title: Title from yaml header b
+ uid: b
+ ---
+ docs/c.md: Link to @a
+outputs:
+ base_path/docs/c.json: |
+ {"conceptual":"Link to Title from yaml header a
\n"}
+ base_path/docs/a.json:
+ base_path/docs/b.json:
+ .xrefmap.json: |
+ {
+ "references":[
+ {
+ "uid": "a",
+ "href": "https://review.docs.microsoft.com/base_path/docs/a?branch=main",
+ "name": "Title from yaml header a"
+ },
+ {
+ "uid": "b",
+ "href": "https://review.docs.microsoft.com/base_path/docs/b?branch=main",
+ "name": "Title from yaml header b"
+ }
+ ]
+ }
+---
+# [alternativeHostName][resolve][before] resolve external xref href url and replaced with main hostname
+# as alternative hostname may be not available
+# remove url hostname if it matches hostname/alternativeHostname/xrefHostname/'alternativeXrefHostname'
+BuildEnvironment: Prod
+repos:
+ https://docs.com/test-alternativeHostName-before-resolve#main:
+ - files:
+ docfx.yml: |
+ hostName: docs.microsoft.com
+ xrefHostName: review.docs.microsoft.com
+ alternativeHostName: new-docs.microsoft.com
+ xref:
+ - 1.xrefmap.json
+ docs/a.md: Link to @System.String
+ docs/a.old.md: Link to @System.String.old
+ docs/b.md: Link to @System.String2
+ docs/b.old.md: Link to @System.String2.old
+ docs/c.md: Link to @System.String3
+ 1.xrefmap.json: |
+ {
+ "references":[
+ {
+ "uid": "System.String",
+ "name": "String",
+ "href": "https://new-docs.microsoft.com/dotnet/api/system.string"
+ },
+ {
+ "uid": "System.String.old",
+ "name": "String.old",
+ "href": "https://docs.microsoft.com/dotnet/api/system.string.old"
+ },
+ {
+ "uid": "System.String2",
+ "name": "String2",
+ "href": "https://review.new-docs.microsoft.com/dotnet/api/system.string2"
+ },
+ {
+ "uid": "System.String2.old",
+ "name": "String2.old",
+ "href": "https://review.docs.microsoft.com/dotnet/api/system.string2.old"
+ },
+ {
+ "uid": "System.String3",
+ "name": "String3",
+ "href": "https://unknown-docs-3.microsoft.com/dotnet/api/system.string3"
+ }
+ ]
+ }
+outputs:
+ docs/a.json: |
+ { "conceptual": "Link to String
\n"}
+ docs/a.old.json: |
+ { "conceptual": "Link to String.old
\n"}
+ docs/b.json: |
+ { "conceptual": "Link to String2
\n"}
+ docs/b.old.json: |
+ { "conceptual": "Link to String2.old
\n"}
+ docs/c.json: |
+ { "conceptual": "Link to String3
\n"}
+---
+
+# [alternativeHostName][xrefmap][after] output xrefmap with main hostname
+# as alternative hostname may be not available
+repos:
+ https://docs.com/test-alternativeHostName-xrefmap-after#live:
+ - files:
+ docfx.yml: |
+ hostName: new-docs.microsoft.com
+ basePath: /base_path
+ alternativeHostName: docs.microsoft.com
+ docs/a.md: |
+ ---
+ title: Title from yaml header a
+ uid: a
+ ---
+ docs/b.md: |
+ ---
+ title: Title from yaml header b
+ uid: b
+ ---
+ docs/c.md: Link to @a
+outputs:
+ base_path/docs/c.json: |
+ {"conceptual":"Link to Title from yaml header a
\n"}
+ base_path/docs/a.json:
+ base_path/docs/b.json:
+ .xrefmap.json: |
+ {
+ "references":[
+ {
+ "uid": "a",
+ "href": "https://new-docs.microsoft.com/base_path/docs/a",
+ "name": "Title from yaml header a"
+ },
+ {
+ "uid": "b",
+ "href": "https://new-docs.microsoft.com/base_path/docs/b",
+ "name": "Title from yaml header b"
+ }
+ ]
+ }
+---
+# [alternativeHostName][xrefmap][after] output xrefmap with xrefhostName if xrefhostName presents
+# as alternative hostname may be not available
+BuildEnvironment: Prod
+repos:
+ https://docs.com/test-alternativeHostName-after0#main:
+ - files:
+ docfx.yml: |
+ hostName: new-docs.microsoft.com
+ xrefhostName: review.new-docs.microsoft.com
+ basePath: /base_path
+ alternativeHostName: docs.microsoft.com
+ docs/a.md: |
+ ---
+ title: Title from yaml header a
+ uid: a
+ ---
+ docs/b.md: |
+ ---
+ title: Title from yaml header b
+ uid: b
+ ---
+ docs/c.md: Link to @a
+outputs:
+ base_path/docs/c.json: |
+ {"conceptual":"Link to Title from yaml header a
\n"}
+ base_path/docs/a.json:
+ base_path/docs/b.json:
+ .xrefmap.json: |
+ {
+ "references":[
+ {
+ "uid": "a",
+ "href": "https://review.new-docs.microsoft.com/base_path/docs/a?branch=main",
+ "name": "Title from yaml header a"
+ },
+ {
+ "uid": "b",
+ "href": "https://review.new-docs.microsoft.com/base_path/docs/b?branch=main",
+ "name": "Title from yaml header b"
+ }
+ ]
+ }
+---
+# [alternativeHostName][resolve][after] resolve external xref href url and replaced with main hostname
+# as alternative hostname may be not available
+# remove url hostname if it matches hostname/alternativeHostname/xrefHostname/'alternativeXrefHostname'
+BuildEnvironment: Prod
+repos:
+ https://docs.com/test-alternativeHostName-after2#main:
+ - files:
+ docfx.yml: |
+ hostName: new-docs.microsoft.com
+ xrefHostName: review.new-docs.microsoft.com
+ alternativeHostName: docs.microsoft.com
+ xref:
+ - 1.xrefmap.json
+ docs/a.md: Link to @System.String
+ docs/a.old.md: Link to @System.String.old
+ docs/b.md: Link to @System.String2
+ docs/b.old.md: Link to @System.String2.old
+ docs/c.md: Link to @System.String3
+ 1.xrefmap.json: |
+ {
+ "references":[
+ {
+ "uid": "System.String",
+ "name": "String",
+ "href": "https://new-docs.microsoft.com/dotnet/api/system.string"
+ },
+ {
+ "uid": "System.String.old",
+ "name": "String.old",
+ "href": "https://docs.microsoft.com/dotnet/api/system.string.old"
+ },
+ {
+ "uid": "System.String2",
+ "name": "String2",
+ "href": "https://review.new-docs.microsoft.com/dotnet/api/system.string2"
+ },
+ {
+ "uid": "System.String2.old",
+ "name": "String2.old",
+ "href": "https://review.docs.microsoft.com/dotnet/api/system.string2.old"
+ },
+ {
+ "uid": "System.String3",
+ "name": "String3",
+ "href": "https://unknown-docs-3.microsoft.com/dotnet/api/system.string3"
+ }
+ ]
+ }
+outputs:
+ docs/a.json: |
+ { "conceptual": "Link to String
\n"}
+ docs/a.old.json: |
+ { "conceptual": "Link to String.old
\n"}
+ docs/b.json: |
+ { "conceptual": "Link to String2
\n"}
+ docs/b.old.json: |
+ { "conceptual": "Link to String2.old
\n"}
+ docs/c.json: |
+ { "conceptual": "Link to String3
\n"}
+---
diff --git a/src/docfx/build/link/LinkResolver.cs b/src/docfx/build/link/LinkResolver.cs
index b91b5908172..285de94e427 100644
--- a/src/docfx/build/link/LinkResolver.cs
+++ b/src/docfx/build/link/LinkResolver.cs
@@ -142,7 +142,6 @@ public LinkResolver(
ValidateLink(inclusionRoot, linkNode);
if (linkType == LinkType.External)
{
- var resolvedHref = _config.RemoveHostName ? UrlUtility.RemoveLeadingHostName(href, _config.HostName) : href;
if (_config.TrustedDomains.TryGetValue(tagName, out var domains) && !domains.IsTrusted(href, out var untrustedDomain))
{
if (tagName == "img")
@@ -155,6 +154,8 @@ public LinkResolver(
}
return (errors, "", fragment, LinkType.AbsolutePath, null, false);
}
+ var resolvedHref = _config.RemoveHostName ? UrlUtility.RemoveLeadingHostName(href, _config.HostName) : href;
+ resolvedHref = UrlUtility.RemoveLeadingHostName(resolvedHref, _config.AlternativeHostName, true);
return (errors, resolvedHref, fragment, LinkType.AbsolutePath, null, false);
}
diff --git a/src/docfx/build/redirection/RedirectionProvider.cs b/src/docfx/build/redirection/RedirectionProvider.cs
index 877acfc19a2..15cd85a786e 100644
--- a/src/docfx/build/redirection/RedirectionProvider.cs
+++ b/src/docfx/build/redirection/RedirectionProvider.cs
@@ -95,14 +95,14 @@ public FilePath GetOriginalFile(FilePath file)
using (Progress.Start("Loading redirections"))
{
var redirections = LoadRedirectionModel();
- var redirectUrls = GetRedirectUrls(redirections, _config.HostName);
+ var redirectUrls = GetRedirectUrls(redirections);
var redirectPaths = redirectUrls.Keys.Select(x => x.Path).ToHashSet();
return (redirectUrls, redirectPaths, redirections);
}
}
- private Dictionary GetRedirectUrls(RedirectionItem[] redirections, string hostName)
+ private Dictionary GetRedirectUrls(RedirectionItem[] redirections)
{
var redirectUrls = new Dictionary();
@@ -127,23 +127,21 @@ private Dictionary GetRedirectUrls(RedirectionItem[] redirecti
var monikers = item.Monikers is null ? default : _monikerProvider.Validate(_errors, item.Monikers);
var filePath = FilePath.Redirection(path, monikers);
- if (item.RedirectDocumentId)
+ switch (UrlUtility.GetLinkType(absoluteRedirectUrl))
{
- switch (UrlUtility.GetLinkType(absoluteRedirectUrl))
- {
- case LinkType.RelativePath:
- var siteUrl = _documentProvider.GetSiteUrl(filePath);
- absoluteRedirectUrl = PathUtility.Normalize(Path.Combine(Path.GetDirectoryName(siteUrl) ?? "", absoluteRedirectUrl));
- break;
- case LinkType.AbsolutePath:
- break;
- case LinkType.External:
- absoluteRedirectUrl = UrlUtility.RemoveLeadingHostName(absoluteRedirectUrl, hostName, removeLocale: true);
- break;
- default:
- _errors.Add(Errors.Redirection.RedirectUrlInvalid(path, redirectUrl));
- break;
- }
+ case LinkType.RelativePath:
+ var siteUrl = _documentProvider.GetSiteUrl(filePath);
+ absoluteRedirectUrl = PathUtility.Normalize(Path.Combine(Path.GetDirectoryName(siteUrl) ?? "", absoluteRedirectUrl));
+ break;
+ case LinkType.AbsolutePath:
+ break;
+ case LinkType.External:
+ absoluteRedirectUrl = UrlUtility.RemoveLeadingHostName(absoluteRedirectUrl, _config.HostName, removeLocale: true);
+ absoluteRedirectUrl = UrlUtility.RemoveLeadingHostName(absoluteRedirectUrl, _config.AlternativeHostName, removeLocale: true);
+ break;
+ default:
+ _errors.Add(Errors.Redirection.RedirectUrlInvalid(path, redirectUrl));
+ break;
}
if (!redirectUrls.TryAdd(filePath, absoluteRedirectUrl))
diff --git a/src/docfx/build/xref/XrefResolver.cs b/src/docfx/build/xref/XrefResolver.cs
index a3d539447ab..e42eaffdd6d 100644
--- a/src/docfx/build/xref/XrefResolver.cs
+++ b/src/docfx/build/xref/XrefResolver.cs
@@ -15,10 +15,12 @@ internal class XrefResolver
private readonly DependencyMapBuilder _dependencyMapBuilder;
private readonly FileLinkMapBuilder _fileLinkMapBuilder;
private readonly Repository? _repository;
+
+ // the hostname used in output xrefmap
private readonly string _xrefHostName;
+
private readonly InternalXrefMapBuilder _internalXrefMapBuilder;
private readonly Func _jsonSchemaTransformer;
-
private readonly Watch _externalXrefMap;
private readonly Watch<(IReadOnlyDictionary xrefsByUid,
IReadOnlyDictionary xrefsByFilePath)> _internalXrefMap;
@@ -48,6 +50,7 @@ public XrefResolver(
_dependencyMapBuilder = dependencyMapBuilder;
_fileLinkMapBuilder = fileLinkMapBuilder;
_xrefHostName = string.IsNullOrEmpty(config.XrefHostName) ? config.HostName : config.XrefHostName;
+
_internalXrefMapBuilder = new(
config,
errorLog,
@@ -281,21 +284,32 @@ private void ValidateExternalXref(IReadOnlyDictionary RedirectionFiles { get; init; } = new();
+ public string AlternativeHostName { get; init; } = string.Empty;
+
public IEnumerable> GetFileReferences()
{
foreach (var url in Xref)
diff --git a/src/docfx/config/EnvironmentVariable.cs b/src/docfx/config/EnvironmentVariable.cs
index 3985daa2999..a1173a9847c 100644
--- a/src/docfx/config/EnvironmentVariable.cs
+++ b/src/docfx/config/EnvironmentVariable.cs
@@ -27,6 +27,9 @@ public static class EnvironmentVariable
public static string? SessionId => GetValue("DOCFX_SESSION_ID");
+ // TODO: remove after switch complete
+ public static string? PPEDefaultDomainHostName => GetValue("DOCFX_PPE_DEFAULT_DOMAIN_HOST_NAME");
+
private static string? GetValue(string name)
{
var value = Environment.GetEnvironmentVariable(name);
diff --git a/src/docfx/config/TestQuirks.cs b/src/docfx/config/TestQuirks.cs
index a267a6e46c7..263cbe08f5e 100644
--- a/src/docfx/config/TestQuirks.cs
+++ b/src/docfx/config/TestQuirks.cs
@@ -7,6 +7,8 @@ internal static class TestQuirks
{
public static Func? AppDataPath { get; set; }
+ public static Func? BuildEnvironment { get; set; }
+
public static Func? GitRemoteProxy { get; set; }
public static Func? HttpProxy { get; set; }
diff --git a/src/docfx/config/ops/OpsConfigAdapter.cs b/src/docfx/config/ops/OpsConfigAdapter.cs
index a01858c7655..12f8703f7dd 100644
--- a/src/docfx/config/ops/OpsConfigAdapter.cs
+++ b/src/docfx/config/ops/OpsConfigAdapter.cs
@@ -72,6 +72,13 @@ public OpsConfigAdapter(OpsAccessor opsAccessor)
return null;
}
+ public static string GetXrefHostNameByHostName(string hostName, string? branch = null, DocsEnvironment? env = null)
+ {
+ return (branch != "live" && (env ?? OpsAccessor.DocsEnvironment) == DocsEnvironment.Prod)
+ ? $"review.{hostName}"
+ : hostName;
+ }
+
private async Task GetBuildConfig(Uri url)
{
var queries = HttpUtility.ParseQueryString(url.Query);
@@ -119,7 +126,7 @@ private async Task GetBuildConfig(Uri url)
xrefMaps.AddRange(links);
}
- var xrefHostName = GetXrefHostName(docset.site_name, branch);
+ var xrefHostName = GetXrefHostNameBySiteName(docset.site_name, branch);
var documentUrls = JsonConvert.DeserializeAnonymousType(
await _opsAccessor.GetDocumentUrls(), new[] { new { log_code = "", document_url = "" } })
?.ToDictionary(item => item.log_code, item => item.document_url);
@@ -223,13 +230,13 @@ private static string GetHostName(string siteName)
_ => OpsAccessor.DocsEnvironment switch
{
DocsEnvironment.Prod => "docs.microsoft.com",
- _ => "ppe.docs.microsoft.com",
+ _ => EnvironmentVariable.PPEDefaultDomainHostName ?? "ppe.docs.microsoft.com",
},
};
}
- private static string GetXrefHostName(string siteName, string branch)
+ private static string GetXrefHostNameBySiteName(string siteName, string branch)
{
- return branch != "live" && OpsAccessor.DocsEnvironment == DocsEnvironment.Prod ? $"review.{GetHostName(siteName)}" : GetHostName(siteName);
+ return GetXrefHostNameByHostName(GetHostName(siteName), branch);
}
}
diff --git a/src/docfx/docfx.csproj b/src/docfx/docfx.csproj
index 8ee836fc3f3..df818f21011 100644
--- a/src/docfx/docfx.csproj
+++ b/src/docfx/docfx.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/test/docfx.SpecTest/DocfxTest.cs b/test/docfx.SpecTest/DocfxTest.cs
index 372e9543b4e..9655e4a91ce 100644
--- a/test/docfx.SpecTest/DocfxTest.cs
+++ b/test/docfx.SpecTest/DocfxTest.cs
@@ -18,9 +18,12 @@ public static class DocfxTest
private static readonly AsyncLocal> s_repos = new();
private static readonly AsyncLocal> s_remoteFiles = new();
private static readonly AsyncLocal s_appDataPath = new();
+ private static readonly AsyncLocal s_buildEnvironment = new();
static DocfxTest()
{
+ TestQuirks.BuildEnvironment = () => s_buildEnvironment.Value;
+
TestQuirks.AppDataPath = () => s_appDataPath.Value;
TestQuirks.GitRemoteProxy = remote =>
@@ -80,6 +83,7 @@ public static void Run(TestData test, DocfxTestSpec spec)
try
{
+ s_buildEnvironment.Value = Enum.TryParse(spec.BuildEnvironment, out DocsEnvironment env) ? env : DocsEnvironment.PPE;
s_repos.Value = repos;
s_remoteFiles.Value = spec.Http;
s_appDataPath.Value = appDataPath;
@@ -95,6 +99,7 @@ public static void Run(TestData test, DocfxTestSpec spec)
}
finally
{
+ s_buildEnvironment.Value = null;
s_repos.Value = null;
s_remoteFiles.Value = null;
s_appDataPath.Value = null;
diff --git a/test/docfx.SpecTest/DocfxTestSpec.cs b/test/docfx.SpecTest/DocfxTestSpec.cs
index e74cc158d6f..c3ebca9036a 100644
--- a/test/docfx.SpecTest/DocfxTestSpec.cs
+++ b/test/docfx.SpecTest/DocfxTestSpec.cs
@@ -31,6 +31,8 @@ public class DocfxTestSpec
public string Locale { get; set; }
+ public string BuildEnvironment { get; set; } = "PPE";
+
[JsonConverter(typeof(OneOrManyConverter))]
public string[] BuildFiles { get; set; } = Array.Empty();