diff --git a/.github/workflows/siemens-internal-build-and-deploy.yml b/.github/workflows/siemens-internal-build-and-deploy.yml new file mode 100644 index 00000000000..d98b7edb25f --- /dev/null +++ b/.github/workflows/siemens-internal-build-and-deploy.yml @@ -0,0 +1,115 @@ +name: Siemens Internal Build and Deploy + +permissions: + contents: read + +on: + # Allow manual runs from the Actions tab for testing without pushing to production/development + workflow_dispatch: + inputs: + dry_run: + description: 'Perform a dry run - do not upload any data' + required: true + default: true + type: boolean + +jobs: + build: + runs-on: ubuntu-22.04 + if: github.repository_owner == 'mendix' + steps: + - name: Checkout repo + uses: actions/checkout@v6 + with: + # 20000 commits required for Hugo to populate last-modified dates on pages + fetch-depth: 20000 + submodules: false + filter: blob:none + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Report Hugo config + run: ./node_modules/.bin/hugo config --environment siemens-internal + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Build with Hugo + run: | + mkdir -p Built/Mendix-Docs/public + set -o pipefail + ./node_modules/.bin/hugo --environment siemens-internal 2>&1 | \ + sed 's/.*│ EN.*/```\n&/; s/.*Cleaned.*/&\n```/' | tee hugo.log + + - name: Add index.html to directory links + run: | + bash _scripts/add-index-html-links.sh Built/Mendix-Docs/public \ + https://internal.docs.sw.siemens.com/documentation/internal/PL20260323299104942/en-US/Mendix-Docs/public/ + + - name: Run htmltest + # htmltest errors are treated as warnings — build continues regardless (matches Travis behaviour) + run: | + chmod +x ./htmltest/htmltest + set +o pipefail + ./htmltest/htmltest --conf .htmltest-siemens.yml 2>&1 | \ + sed 's/\o033\[[0-9;]*[A-Za-z]//g; s/^=\+$/\n&/' | tee -a hugo.log + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + # add blank output line first as ::warning:: does not always trigger a warning annotation + echo " " + echo "::warning::htmltest found broken cross-references. See the Run htmltest step for details." + fi + + - name: Print build log to GitHub Summary + if: always() + run: cat hugo.log >> $GITHUB_STEP_SUMMARY + + - name: Upload public site artifact + uses: actions/upload-artifact@v7 + with: + name: public-site + path: Built + if-no-files-found: error + + deploy: + runs-on: ubuntu-22.04 + needs: build + steps: + - name: Checkout repo + uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Download public site artifact + uses: actions/download-artifact@v8 + with: + name: public-site + path: Built + + - name: Set DRY_RUN + # Set the DRY_RUN environmental variable to indicate if dry_run input is set to true. + run: | + if [ "${{ inputs.dry_run }}" = "false" ]; then + echo "DRY_RUN=" >> "$GITHUB_ENV" + else + echo "DRY_RUN=--dryrun" >> "$GITHUB_ENV" + fi + + - name: Upload to Siemens Support Center S3 + # Use bash script to upload site to Siemens Support Center S3 + # Use SSC secrets for Siemens Support Center - there are separate credentials for docs.mendix.com + # Secrets are restored at the Repository level. + run: bash _scripts/deploy-to-siemens.sh + env: + AWS_ACCESS_KEY_ID: ${{ secrets.SSC_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.SSC_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.SSC_AWS_DEFAULT_REGION }} diff --git a/.htmltest-siemens.yml b/.htmltest-siemens.yml new file mode 100644 index 00000000000..3f8cd937f90 --- /dev/null +++ b/.htmltest-siemens.yml @@ -0,0 +1,50 @@ +# ========================== +# Configuration for HTMLTEST +# ========================== +# See documentation in repo: https://github.com/wjdp/htmltest +# Siemens-internal variant — points to the siemens-internal publishDir + +DirectoryPath: "./Built/Mendix-Docs/public" +DirectoryIndex: "index.html" +FileExtension: .html +# Do not check external files - will want to do this monthly or so - implement later +CheckExternal: false +CheckMailto: false +# Not checking scripts for speed - should be OK as generated through HUGO +CheckScripts: false +CheckTel: false +IgnoreAltMissing: true +IgnoreDirectoryMissingTrailingSlash: false +IgnoreDirs: +- "_includes" +- "_print" +- "attachments" +- "css" +- "favicons" +- "icons" +- "js" +- "scss" +- "webfonts" +IgnoreEmptyHref: true +IgnoreInternalEmptyHash: true +IgnoreInternalURLs: +- "/misc/js/script.js" +- "/js/" +# HUGO generates a lot of tags which don't need to be tested +IgnoreTagAttribute: "data-proofer-ignore" +# Ignore URLs which return a 401/403 authentication error - might want to check these ignored URLs occasionally to ensure they are still valid. +# NOTE DOUBLE \\ needed to escape special characters in the string +IgnoreURLs: +- "example.com" +- "support.mendix.com.*" +- "sapes5.sapdevcenter.com/sap/opu/odata/iwbep/GWSAMPLE_BASIC.*" +- "www.microsoft.com/en-us/download/details.aspx\\?id=11774" + +# ========================================================== +# Speed up using concurrent testing by changing flag to true +# Using 4 concurrent documents halves the processing time +# EXPERIMENTAL - REMOVE IF THERE ARE PROBLEMS +#=========================================================== + +TestFilesConcurrently: false +DocumentConcurrencylimit: 4 diff --git a/_scripts/add-index-html-links.sh b/_scripts/add-index-html-links.sh new file mode 100644 index 00000000000..64da55347ff --- /dev/null +++ b/_scripts/add-index-html-links.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Post-processing script for Siemens internal deployment. +# Rewrites directory-style href links (ending with /) to explicit /index.html +# links in all HTML files under public/, so the Siemens server can serve them +# without requiring automatic directory index support. +# +# When built with the siemens-internal environment, canonifyURLs=true causes Hugo +# to expand all internal links to full absolute URLs using the baseURL. Pass that +# baseURL as the second argument so those links are rewritten too. +# +# Rewrites both plain directory links and directory links with anchors: +# href=".../path/" → href=".../path/index.html" +# href=".../path/#anchor" → href=".../path/index.html#anchor" +# +# Skips: +# - External links (contain :// but do not start with base-url) +# - Anchor-only links starting with # +# - Links already containing index.html or ending in .html/.htm +# +# Usage: bash _scripts/add-index-html-links.sh [public-dir] [base-url] +# Default public-dir: public +# Default base-url: (empty — only root-relative and relative links are rewritten) +# +# Example (siemens-internal build): +# bash _scripts/add-index-html-links.sh public \ +# https://internal.docs.sw.siemens.com/documentation/internal/PL20260323299104942/en-US/Mendix-Docs/public/ + +set -euo pipefail + +PUBLIC_DIR="${1:-public}" +BASE_URL="${2:-}" + +if [ ! -d "$PUBLIC_DIR" ]; then + echo "Error: directory '$PUBLIC_DIR' not found. Run hugo build first." >&2 + exit 1 +fi + +echo "Adding index.html to directory links in $PUBLIC_DIR..." +[ -n "$BASE_URL" ] && echo "Treating '$BASE_URL' as internal base URL." + +python3 - "$PUBLIC_DIR" "$BASE_URL" << 'PYTHON' +import re, sys +from pathlib import Path + +public_dir = sys.argv[1] +base_url = sys.argv[2].rstrip("/") + "/" if sys.argv[2] else "" + +HREF_RE = re.compile(r"""(href=["'])([^"']*)(["'])""") + +def rewrite_href(m): + pre, url, quote = m.group(1), m.group(2), m.group(3) + + # Treat absolute URLs that start with base_url as internal; skip all others + if "://" in url: + if not (base_url and url.startswith(base_url)): + return m.group(0) + + # Skip anchor-only links + if url.startswith("#"): + return m.group(0) + + # Split off any fragment (e.g. /path/#anchor → path=/path/, fragment=#anchor) + fragment = "" + if "#" in url: + url, fragment = url.split("#", 1) + fragment = "#" + fragment + + # Skip already-explicit file links + if url.endswith(".html") or url.endswith(".htm"): + return f"{pre}{url}{fragment}{quote}" + + if url.endswith("/"): + url = url + "index.html" + return f"{pre}{url}{fragment}{quote}" + +count = 0 +for path in Path(public_dir).rglob("*.html"): + original = path.read_text(encoding="utf-8", errors="replace") + updated = HREF_RE.sub(rewrite_href, original) + if updated != original: + path.write_text(updated, encoding="utf-8") + count += 1 + +print(f"Updated {count} HTML files.") +PYTHON diff --git a/_scripts/deploy-to-siemens.sh b/_scripts/deploy-to-siemens.sh new file mode 100644 index 00000000000..1f279266905 --- /dev/null +++ b/_scripts/deploy-to-siemens.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -ex + +BUCKET=scp-prod-source +SIEMENS_EMAIL=mark.van.ments@siemens.com +RELEASE_ID=PL20260323299104942 +LANG_CODE=en-US +TARGETAWSBUCKET=$BUCKET/$SIEMENS_EMAIL/$RELEASE_ID/$LANG_CODE/ + +echo "Deploying to Siemens AWS bucket $TARGETAWSBUCKET" + + + +mkdir -p Built/Mendix-Docs/.meta +cp static/siemens-support-center-metadata/content.xlsx Built/Mendix-Docs/.meta/ + +cd Built +pwd +ls +# the AWS CLI is part of the standard GitHub runner +aws --version + +# Requires the following environment variables (set as GitHub Actions secrets): +# AWS_ACCESS_KEY_ID +# AWS_SECRET_ACCESS_KEY +# AWS_DEFAULT_REGION +# +# HUGO creates new files with a newer timestamp except those in the /static folder +# so this will always push all the html, but only changed /static files. +# +# Need to use old method - or a new method to reduce number of docs transferred. +# see https://stackoverflow.com/questions/1964470/whats-the-equivalent-of-subversions-use-commit-times-for-git/13284229#13284229 for a possibility +# +start=$SECONDS +echo "Starting sync to AWS" +aws s3 sync . s3://$TARGETAWSBUCKET --delete --only-show-errors --exclude "*.png" $DRY_RUN # sync all files except png files +aws s3 sync . s3://$TARGETAWSBUCKET --delete --only-show-errors --size-only --exclude "*" --include "*.png" $DRY_RUN # sync all png files +echo "Upload to AWS took $((SECONDS - start)) seconds" + +exit 0 \ No newline at end of file diff --git a/assets/scss/_font-face.scss b/assets/scss/_font-face.scss index db27cd448a2..afe1020d3d4 100644 --- a/assets/scss/_font-face.scss +++ b/assets/scss/_font-face.scss @@ -1,35 +1,35 @@ @font-face { // LZ - Added for MxDock by request 2024-01-30 font-family: "noto-sans"; - src: local("noto-sans"), url("/fonts/noto-sans/noto-sans-400.woff2") format("woff2"); + src: local("noto-sans"), url("../fonts/noto-sans/noto-sans-400.woff2") format("woff2"); font-style: normal; font-weight: 400; font-display: swap; } - + @font-face { font-family: "noto-sans"; - src: local("noto-sans"), url("/fonts/noto-sans/noto-sans-400-italic.woff2") format("woff2"); + src: local("noto-sans"), url("../fonts/noto-sans/noto-sans-400-italic.woff2") format("woff2"); font-style: italic; font-weight: 400; font-display: swap; } - + @font-face { font-family: "noto-sans"; - src: local("noto-sans"), url("/fonts/noto-sans/noto-sans-600.woff2") format("woff2"); + src: local("noto-sans"), url("../fonts/noto-sans/noto-sans-600.woff2") format("woff2"); font-style: normal; font-weight: 600; font-display: swap; } - + @font-face { font-family: "noto-sans"; - src: local("noto-sans"), url("/fonts/noto-sans/noto-sans-600-italic.woff2") format("woff2"); + src: local("noto-sans"), url("../fonts/noto-sans/noto-sans-600-italic.woff2") format("woff2"); font-style: italic; font-weight: 600; font-display: swap; } - + /* Medium */ @font-face { font-family: "Patron"; @@ -37,9 +37,9 @@ font-weight: 500; font-display: swap; src: local("Patron Medium"), local("Patron-Medium"), - url("/fonts/patron/PatronWEB-Medium.woff2") format("woff2"); + url("../fonts/patron/PatronWEB-Medium.woff2") format("woff2"); } - + /* Light */ @font-face { font-family: "Patron"; @@ -47,7 +47,7 @@ font-weight: 300; font-display: swap; src: local("Patron Light"), local("Patron-Light"), - url("/fonts/patron/PatronWEB-Light.woff2") format("woff2"); + url("../fonts/patron/PatronWEB-Light.woff2") format("woff2"); } /* The main font is Noto Sans */ diff --git a/config/siemens-internal/README.md b/config/siemens-internal/README.md new file mode 100644 index 00000000000..765322588d0 --- /dev/null +++ b/config/siemens-internal/README.md @@ -0,0 +1,117 @@ +# Siemens Internal Deployment Configuration + +This directory contains the Hugo environment configuration for deploying the Mendix documentation to the Siemens internal documentation portal. + +## Deployment URL + +The site is deployed at: + +``` +https://internal.docs.sw.siemens.com/documentation/internal/PL20260323299104942/en-US/Mendix-Docs/public/ +``` + +## The Problem: Pretty URLs Not Supported + +The Siemens internal server does not serve Hugo's default "pretty URLs" (like `/page/` resolving to `/page/index.html`). `uglyURLs = true` helps, but Hugo still generates many directory-style links (ending with `/`) in navigation menus, breadcrumbs, and table-of-contents entries. + +A post-processing script rewrites all remaining directory-style `href` links to include `/index.html` explicitly. + +## The Solution + +Configure Hugo with the appropriate settings for deep URL deployment, then run the post-processing script: + +1. **Set the full baseURL** including the deep path +2. **Enable `canonifyURLs = true`** to convert all root-relative URLs to use the baseURL +3. **Enable `uglyURLs = true`** to reduce (but not eliminate) directory-style links +4. **Use relative font paths** in CSS (`../fonts/` instead of `/fonts/`) to work across all environments +5. **Run `add-index-html-links.sh`** to rewrite remaining `href=".../"` links to `href=".../index.html"` + +## How to Build + +Run these commands from the repository root: + +```bash +# Build the site with the siemens-internal environment +Hugo --environment siemens-internal --cleanDestinationDir + +# Rewrite directory-style links to include index.html. +# The second argument (the site's baseURL) is required: the siemens-internal build uses +# canonifyURLs=true, which expands all internal links to full absolute URLs. Without the +# baseURL argument, the script cannot tell internal links from external ones and skips them. +bash _scripts/add-index-html-links.sh Built/Mendix-Docs/public \ + https://internal.docs.sw.siemens.com/documentation/internal/PL20260323299104942/en-US/Mendix-Docs/public/ +``` + +The built site will be in `Built/Mendix-Docs/public/`, ready for deployment to the Siemens internal portal. + +## GitHub Actions Workflow + +The `siemens-internal-build-and-deploy` workflow (`.github/workflows/siemens-internal-build-and-deploy.yml`) automates the build and deployment to the Siemens Support Center S3 bucket. It is triggered manually from the Actions tab and has a single input: + +* **`dry_run`** (Boolean, default `true`): when `true`, the S3 sync runs with `--dryrun` and no files are uploaded. Set to `false` to perform a real deployment. + +The workflow runs in two jobs: + +### Build Job + +1. Checks out the repository (with 20,000 commits to populate last-modified dates). +2. Installs Node.js dependencies and Python. +3. Builds the site with Hugo using the `siemens-internal` environment, outputting to `Built/Mendix-Docs/public/`. +4. Runs `add-index-html-links.sh` to rewrite directory-style links. +5. Runs htmltest (using `.htmltest-siemens.yml`) to check for broken cross-references; failures are treated as warnings. +6. Uploads the entire `Built/` directory as a build artifact. + +### Deploy Job + +1. Checks out the repository (for access to scripts and metadata files). +2. Downloads the `Built/` artifact from the build job. +3. Sets the `DRY_RUN` environment variable to `--dryrun` (default) or an empty string based on the `dry_run` input. +4. Runs `_scripts/deploy-to-siemens.sh`, which copies `content.xlsx` into `Built/Mendix-Docs/.meta/` and syncs the `Built/` directory to the Siemens Support Center S3 bucket using the `SSC_AWS_*` secrets. + +The workflow is guarded by `if: github.repository_owner == 'mendix'` on the build job, so it will not run in forks. + +## Configuration Files + +### hugo.toml + +Sets the baseURL, the publish directory, and enables: + +* `publishDir = "Built/Mendix-Docs/public"` to match the structure expected by the Siemens Support Center +* `canonifyURLs = true` to handle the deep deployment path +* `uglyURLs = true` to ensure proper URL resolution on the Siemens server + +### _scripts/add-index-html-links.sh + +Rewrites all `href=".../"` directory-style links in the built HTML to `href=".../index.html"` so the Siemens server can serve them without automatic directory index support. Run this after every Hugo build. + +Skips external links, anchor links, links that already end in `.html`, and print URLs. + +## Technical Details + +### Why CanonifyURLs? + +The `canonifyURLs = true` setting converts all root-relative URLs (like `/images/foo.svg`) to absolute URLs using the baseURL. This is necessary because: + +* Images in templates use hardcoded paths like `/images/...` and `/icons/...` +* Internal page links need the full path +* Without it, all these references would be broken + +### Why Relative Font Paths? + +Font files are referenced in CSS using relative paths (`../fonts/`, `../webfonts/`) rather than root-relative paths (`/fonts/`). This approach: + +* Works consistently across all deployment environments (production, development, siemens-internal) +* Does not require environment-specific processing +* Avoids issues with deep URL paths + +## Updating the Deployment Path + +If the Siemens deployment URL changes, update the `baseURL` in `config/siemens-internal/hugo.toml`. + +## Alternative Approaches Considered + +1. **Using relativeURLs**: Breaks the landing page images and requires template changes +2. **Path-only baseURL**: Requires web server configuration and does not work with direct file access +3. **Template modifications**: Requires maintaining custom versions of Docsy theme files + +The current solution (Hugo configuration plus a focused post-processing script) is the simplest and most maintainable approach. diff --git a/config/siemens-internal/hugo.toml b/config/siemens-internal/hugo.toml new file mode 100644 index 00000000000..ab1d945fa4b --- /dev/null +++ b/config/siemens-internal/hugo.toml @@ -0,0 +1,20 @@ +# Merges with _default/config.toml if --environment internal +# Configuration for internal Siemens documentation deployment +# Need to set _merge = 'deep' to overwrite original value +# ============================================================= + +baseURL = "https://internal.docs.sw.siemens.com/documentation/internal/PL20260323299104942/en-US/Mendix-Docs/public/" +title = "Mendix Documentation (Internal)" + +# Convert all absolute paths (starting with /) to use the full baseURL +# This fixes images and most links. The CSS path doubling issue can be fixed with post-processing. +canonifyURLs = true + +# Disable robots.txt for internal deployment +enableRobotsTXT = false + +# Enable ugly URLs to help links work on the Siemens site +uglyURLs = true + +# Publish siemens-internal to "Built/Mendix-Docs/public" so we can add meta data and upload Mendix-Docs to match expected structure of Siemens Support Center +publishDir = "Built/Mendix-Docs/public" \ No newline at end of file diff --git a/static/siemens-support-center-metadata/content.xlsx b/static/siemens-support-center-metadata/content.xlsx new file mode 100644 index 00000000000..7d8c0c183b6 Binary files /dev/null and b/static/siemens-support-center-metadata/content.xlsx differ