diff --git a/dockerfiles/nginx/proxito.conf.template b/dockerfiles/nginx/proxito.conf.template
index 65ee44bb7c8..dfe4fbd18c5 100644
--- a/dockerfiles/nginx/proxito.conf.template
+++ b/dockerfiles/nginx/proxito.conf.template
@@ -88,6 +88,17 @@ server {
proxy_hide_header Content-Security-Policy;
set $content_security_policy $upstream_http_content_security_policy;
add_header Content-Security-Policy $content_security_policy always;
+
+ # This header allows us to decide whether or not inject the script at CloudFlare level
+ # Now, I'm injecting it in all the NGINX responses because `sub_filter` is not allowed inside an `if` statement.
+ set $rtd_hosting_integrations $upstream_http_x_rtd_hosting_integrations;
+ add_header X-RTD-Hosting-Integrations $rtd_hosting_integrations always;
+
+ # Inject our own script dynamically
+ # TODO: find a way to make this work _without_ running `npm run dev` from the `readthedocs-client` repository
+ sub_filter '' '\n';
+ sub_filter_last_modified on;
+ sub_filter_once on;
}
# Serve 404 pages here
diff --git a/readthedocs/api/v2/serializers.py b/readthedocs/api/v2/serializers.py
index 4a5c03e758a..aad6ac57c43 100644
--- a/readthedocs/api/v2/serializers.py
+++ b/readthedocs/api/v2/serializers.py
@@ -101,7 +101,7 @@ class VersionSerializer(serializers.ModelSerializer):
class Meta:
model = Version
- fields = (
+ fields = [
'id',
'project',
'slug',
@@ -116,7 +116,7 @@ class Meta:
'has_epub',
'has_htmlzip',
'documentation_type',
- )
+ ]
class VersionAdminSerializer(VersionSerializer):
@@ -124,6 +124,12 @@ class VersionAdminSerializer(VersionSerializer):
"""Version serializer that returns admin project data."""
project = ProjectAdminSerializer()
+ build_data = serializers.JSONField(required=False, write_only=True)
+
+ class Meta(VersionSerializer.Meta):
+ fields = VersionSerializer.Meta.fields + [
+ "build_data",
+ ]
class BuildCommandSerializer(serializers.ModelSerializer):
diff --git a/readthedocs/builds/migrations/0048_add_build_data.py b/readthedocs/builds/migrations/0048_add_build_data.py
new file mode 100644
index 00000000000..94a68c30fa0
--- /dev/null
+++ b/readthedocs/builds/migrations/0048_add_build_data.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.2.18 on 2023-03-13 15:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("builds", "0047_build_default_triggered"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="version",
+ name="build_data",
+ field=models.JSONField(
+ default=None,
+ null=True,
+ verbose_name="Data generated at build time by the doctool (`readthedocs-build.yaml`).",
+ ),
+ ),
+ ]
diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py
index fafc68cecaa..dc83b11c34b 100644
--- a/readthedocs/builds/models.py
+++ b/readthedocs/builds/models.py
@@ -65,11 +65,9 @@
BITBUCKET_COMMIT_URL,
BITBUCKET_URL,
DOCTYPE_CHOICES,
- GITHUB_BRAND,
GITHUB_COMMIT_URL,
GITHUB_PULL_REQUEST_COMMIT_URL,
GITHUB_URL,
- GITLAB_BRAND,
GITLAB_COMMIT_URL,
GITLAB_MERGE_REQUEST_COMMIT_URL,
GITLAB_URL,
@@ -186,6 +184,12 @@ class Version(TimeStampedModel):
),
)
+ build_data = models.JSONField(
+ _("Data generated at build time by the doctool (`readthedocs-build.yaml`)."),
+ default=None,
+ null=True,
+ )
+
objects = VersionManager.from_queryset(VersionQuerySet)()
# Only include BRANCH, TAG, UNKNOWN type Versions.
internal = InternalVersionManager.from_queryset(partial(VersionQuerySet, internal_only=True))()
diff --git a/readthedocs/doc_builder/director.py b/readthedocs/doc_builder/director.py
index c7aecd62910..3c7e824374a 100644
--- a/readthedocs/doc_builder/director.py
+++ b/readthedocs/doc_builder/director.py
@@ -2,6 +2,7 @@
import tarfile
import structlog
+import yaml
from django.conf import settings
from django.utils.translation import gettext_lazy as _
@@ -187,6 +188,7 @@ def build(self):
self.build_epub()
self.run_build_job("post_build")
+ self.store_readthedocs_build_yaml()
after_build.send(
sender=self.data.version,
@@ -392,6 +394,7 @@ def run_build_commands(self):
# Update the `Version.documentation_type` to match the doctype defined
# by the config file. When using `build.commands` it will be `GENERIC`
self.data.version.documentation_type = self.data.config.doctype
+ self.store_readthedocs_build_yaml()
def install_build_tools(self):
"""
@@ -625,3 +628,37 @@ def get_build_env_vars(self):
def is_type_sphinx(self):
"""Is documentation type Sphinx."""
return "sphinx" in self.data.config.doctype
+
+ def store_readthedocs_build_yaml(self):
+ # load YAML from user
+ yaml_path = os.path.join(
+ self.data.project.artifact_path(
+ version=self.data.version.slug, type_="html"
+ ),
+ "readthedocs-build.yaml",
+ )
+
+ if not os.path.exists(yaml_path):
+ log.debug("Build output YAML file (readtehdocs-build.yaml) does not exist.")
+ return
+
+ try:
+ with open(yaml_path, "r") as f:
+ data = yaml.safe_load(f)
+ except Exception:
+ # NOTE: skip this work for now until we decide whether or not this
+ # YAML file is required.
+ #
+ # NOTE: decide whether or not we want this
+ # file to be mandatory and raise an exception here.
+ return
+
+ log.info("readthedocs-build.yaml loaded.", path=yaml_path)
+
+ # TODO: validate the YAML generated by the user
+ # self._validate_readthedocs_build_yaml(data)
+
+ # Copy the YAML data into `Version.build_data`.
+ # It will be saved when the API is hit.
+ # This data will be used by the `/_/readthedocs-config.json` API endpoint.
+ self.data.version.build_data = data
diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py
index bf502c1fce5..e42a381c3cf 100644
--- a/readthedocs/projects/models.py
+++ b/readthedocs/projects/models.py
@@ -1884,6 +1884,7 @@ def add_features(sender, **kwargs):
CANCEL_OLD_BUILDS = "cancel_old_builds"
DONT_CREATE_INDEX = "dont_create_index"
USE_RCLONE = "use_rclone"
+ HOSTING_INTEGRATIONS = "hosting_integrations"
FEATURES = (
(ALLOW_DEPRECATED_WEBHOOKS, _('Allow deprecated webhook views')),
@@ -2058,6 +2059,10 @@ def add_features(sender, **kwargs):
USE_RCLONE,
_("Use rclone for syncing files to the media storage."),
),
+ (
+ HOSTING_INTEGRATIONS,
+ _("Inject 'readthedocs-client.js' as