Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ jobs:
- build-and-test
permissions:
id-token: write
outputs:
new_release: ${{ steps.check_release.outputs.new_release }}
release_tag: ${{ steps.check_release.outputs.release_tag }}
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -30,6 +33,12 @@ jobs:
name: dist
path: dist
- run: npm ci --prefer-offline --no-audit

- id: before_release
run: |
BEFORE_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
echo "before_tag=$BEFORE_TAG" >> $GITHUB_OUTPUT

- run: npx semantic-release
env:
SKIP_COMMIT: ${{ github.ref_name == 'next' && 'true' || '' }}
Expand All @@ -38,3 +47,191 @@ jobs:
GIT_COMMITTER_NAME: 'Siemens Element Bot'
GIT_COMMITTER_EMAIL: 'simpl.si@siemens.com'
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_GITHUB_TOKEN }}

- id: check_release
run: |
AFTER_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
BEFORE_TAG="${{ steps.before_release.outputs.before_tag }}"

if [[ -n "$AFTER_TAG" && "$AFTER_TAG" != "$BEFORE_TAG" ]]; then
echo "release_tag=$AFTER_TAG" >> $GITHUB_OUTPUT
echo "new_release=true" >> $GITHUB_OUTPUT
else
echo "new_release=false" >> $GITHUB_OUTPUT
echo "release_tag=" >> $GITHUB_OUTPUT
fi

publish-documentation-release:
runs-on: ubuntu-24.04
needs:
- publish
- build-and-test
if: success() && needs.publish.outputs.new_release == 'true' && github.ref_name != 'next'
permissions:
id-token: write
env:
VERSIONED_BUCKET_NAME: simpl-element-release
CLOUDFRONT_DOMAIN: d2uqfzn4lxgtwv.cloudfront.net
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- id: deploy
uses: actions/github-script@v7
with:
script: |
const deployAsRelease = "${{ needs.publish.outputs.release_tag }}";

if (!deployAsRelease) {
core.setFailed("deploy_as_release input is required");
return;
}

const versionRegex = /^v[0-9]+\.[0-9]+\.[0-9]+.*$/;
if (!versionRegex.test(deployAsRelease)) {
core.setFailed(`Error: Deploy as release version must be in format 'v1.2.3' or 'v1.2.3-suffix'\nProvided: '${deployAsRelease}'`);
return;
}

const version = deployAsRelease.substring(1);
const majorVersion = `v${version.split('.')[0]}`;
const isPrerelease = version.includes('-');

const deployMajor = !isPrerelease;
const shouldDeploy = deployMajor;

core.setOutput('deploy_major', deployMajor);
core.setOutput('major_version', majorVersion);
core.setOutput('version', version);
core.setOutput('should_deploy', shouldDeploy);

console.log('DEPLOYMENT PLAN');
console.log('===============');
console.log(`Branch: ${{ github.ref }}`);
console.log(`Trigger: ${{ github.event_name }}`);
console.log(`Major (${majorVersion}): ${deployMajor}`);

if (!shouldDeploy) {
core.setFailed("Skipping deployment (pre-release version)");
}

- uses: actions/download-artifact@v7
with:
name: pages
path: new-docs

- uses: aws-actions/configure-aws-credentials@v5.1.1
with:
role-to-assume: arn:aws:iam::974483672234:role/simpl-element-release
role-session-name: element-release-docs
aws-region: eu-west-1

- run: |
MAJOR_VERSION="${{ steps.deploy.outputs.major_version }}"

echo "Updating /$MAJOR_VERSION/..."
mkdir "deploy-site"
mv new-docs "deploy-site/$MAJOR_VERSION"

- run: |
# Generate versions.json from S3 directory listing (no download needed)
echo "Generating versions.json from S3 directory listing..."

# Collect all versions from S3
ALL_VERSIONS=()

# List all existing version-specific folders from S3 (v1, v2, v48, etc.)
# aws s3 ls lists directories with trailing slashes, e.g., "PRE v1/"
aws s3 ls s3://${{ env.VERSIONED_BUCKET_NAME }}/ | grep "PRE v" | awk '{print $2}' | sed 's/\/$//' > s3-versions.txt || true

# Add the currently deploying version to ensure it's included
MAJOR_VERSION="${{ steps.deploy.outputs.major_version }}"
echo "$MAJOR_VERSION" >> s3-versions.txt

# Read all versions and remove duplicates
while IFS= read -r version_name; do
if [[ -n "$version_name" ]]; then
ALL_VERSIONS+=("$version_name")
fi
done < s3-versions.txt

# Remove duplicates and sort versions in descending order
SORTED_VERSIONS=($(printf '%s\n' "${ALL_VERSIONS[@]}" | sort -u -t 'v' -k 2 -n -r))

# Find the highest version for "Latest"
LATEST_VERSION="${SORTED_VERSIONS[0]}"
echo "Latest version: $LATEST_VERSION"

# Build versions.json with correct order:
# 1. Latest (empty version string)
# 2. All versions in descending order (v48, v18, v17, etc.)
# 3. Preview (redirect to element.siemens.io)

VERSIONS='[]'

# Add Latest first (empty version string points to root)
latest_num=$(echo "$LATEST_VERSION" | sed 's/^v//')
echo "Adding: Latest (${latest_num}.x)"
VERSIONS=$(echo "$VERSIONS" | jq '. += [{"version": "", "title": "Latest"}]')

# Add all versions in descending order
for version_name in "${SORTED_VERSIONS[@]}"; do
version_num=$(echo "$version_name" | sed 's/^v//')
echo "Adding: $version_name (${version_num}.x)"
VERSIONS=$(echo "$VERSIONS" | jq --arg version "$version_name" --arg title "${version_num}.x" '. += [{"version": $version, "title": $title}]')
done

# Add Preview last
echo "Adding: Preview (redirect to https://element.siemens.io)"
VERSIONS=$(echo "$VERSIONS" | jq '. += [{"version": "https://element.siemens.io", "title": "Preview"}]')

# Write to deploy-site/versions.json
echo "$VERSIONS" | jq '.' > deploy-site/versions.json

echo "Generated versions.json:"
cat deploy-site/versions.json

- run: |
SITE_URL="https://element.siemens.io/"

# Update canonical URLs to point to versioned URLs instead of root
# This ensures search engines index the correct versioned documentation (only one version)
MAJOR_VERSION="${{ steps.deploy.outputs.major_version }}"
VERSION_URL="${SITE_URL}${MAJOR_VERSION}/"

find "deploy-site/$MAJOR_VERSION" -name "*.html" -type f -exec sed -i \
-e "s|<link rel=\"canonical\" href=\"https://element.siemens.io/|<link rel=\"canonical\" href=\"${VERSION_URL}|g" \
{} \;

- run: |
echo "Uploading versioned documentation to S3..."

MAJOR_VERSION="${{ steps.deploy.outputs.major_version }}"

# Upload versioned directory
echo "Uploading /$MAJOR_VERSION/..."
if [[ ! -d "deploy-site/$MAJOR_VERSION" ]]; then
echo "Error: deploy-site/$MAJOR_VERSION directory does not exist"
exit 1
fi
aws s3 sync --quiet --no-progress --delete "deploy-site/$MAJOR_VERSION/" "s3://${{ env.VERSIONED_BUCKET_NAME }}/$MAJOR_VERSION/"

# If on main branch, also copy to root (without version prefix in path)
if [[ "${{ github.ref }}" == "refs/heads/${{ github.event.repository.default_branch }}" ]]; then
echo "On main branch - copying /$MAJOR_VERSION/ to root..."
# Delete old files but exclude version directories and versions.json from sync and deletion
aws s3 sync --quiet --no-progress --delete "deploy-site/$MAJOR_VERSION/" "s3://${{ env.VERSIONED_BUCKET_NAME }}/" \
--exclude "v*" \
--exclude "versions.json"
fi

# Upload versions.json with short cache-control for quick updates
if [[ ! -f "deploy-site/versions.json" ]]; then
echo "Error: deploy-site/versions.json file does not exist"
exit 1
fi
aws s3 cp deploy-site/versions.json s3://${{ env.VERSIONED_BUCKET_NAME }}/versions.json \
--cache-control "max-age=60,public"

echo "Uploaded versioned documentation to S3 at s3://${{ env.VERSIONED_BUCKET_NAME }}/"
167 changes: 167 additions & 0 deletions docs/_src/version-selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* Version Selector for Siemens Element Documentation
*
* This script implements a version switcher that:
* - Fetches versions.json from the root of the domain
* - Supports absolute version URLs
* - Preserves current page path when switching versions
* - Gracefully degrades if versions.json is not found (no errors, just no selector)
*
* If versions.json is not available (404), the page loads normally without the version selector.
* No errors are thrown to ensure documentation remains accessible.
*
* Based on MkDocs Material's version selector implementation
* https://github.com/squidfunk/mkdocs-material
*/

(function () {
'use strict';

/**
* Get the base URL of the site (root domain)
*/
function getBaseURL() {
const location = window.location;
return `${location.protocol}//${location.host}`;
}

/**
* Get current version from URL path
* Only recognizes versions that exist in versions.json
* Returns empty string if at root (no version in path)
*/
function getCurrentVersion(versions) {
const path = window.location.pathname;

// If we don't have versions yet, try to extract from path
if (!versions) {
const match = path.match(/\/([^/]+)\//);
return match ? match[1] : '';
}

// Check if any known version is in the path
for (const version of versions) {
if (version.version && path.includes(`/${version.version}/`)) {
return version.version;
}
}

// No version found in path, assume root-level version
return '';
}

/**
* Get current page path relative to version
*/
function getCurrentPagePath(currentVersion) {
const path = window.location.pathname;

// If no version (root-level), return the full path
if (!currentVersion || currentVersion === '') {
return path.substring(1); // Remove leading slash
}

// Find version in path and get everything after it
const versionIndex = path.indexOf(`/${currentVersion}/`);
if (versionIndex !== -1) {
return path.substring(versionIndex + currentVersion.length + 2);
}

return '';
}

/**
* Build version URL with current page path
* Supports versions at root (empty string), in subdirectories, or absolute URLs
*/
function buildVersionURL(version, currentVersion, preservePath = true) {
// If version is an absolute URL, return as-is
if (
version.startsWith('http://') ||
version.startsWith('https://') ||
version.startsWith('//')
) {
return version;
}

const baseURL = getBaseURL();
const pagePath = preservePath ? getCurrentPagePath(currentVersion) : '';

// If version is empty string or "/", host at root
if (!version || version === '/' || version === '') {
return `${baseURL}/${pagePath}`;
}

return `${baseURL}/${version}/${pagePath}`;
}

/**
* Render version selector HTML
*/
function renderVersionSelector(versions, currentVersion) {
const current = versions.find(v => v.version === currentVersion) || versions[0];
const visibleVersions = versions.filter(v => !v.hidden);

const html = `<div class="md-version"><button class="md-version__current" aria-label="Select version">${current.title}</button><ul class="md-version__list">${visibleVersions.map(version => `<li class="md-version__item"><a href="${buildVersionURL(version.version, currentVersion)}" class="md-version__link">${version.title}</a></li>`).join('')}</ul></div>`;

return html;
}

/**
* Initialize version selector
*/
function initVersionSelector() {
const baseURL = getBaseURL();
const versionsURL = `${baseURL}/versions.json`;

fetch(versionsURL)
.then(response => {
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`Failed to fetch versions.json: ${response.status}`);
}
return response.json();
})
.then(versions => {
if (!versions) {
return;
}

if (!Array.isArray(versions) || versions.length === 0) {
console.warn('[Version Selector] versions.json is empty or invalid');
return;
}

// Get current version after we have the versions list
const currentVersion = getCurrentVersion(versions);

// Find the .md-header element
const header = document.querySelector('.md-header');
if (!header) {
console.warn('[Version Selector] .md-header element not found');
return;
}

// Create .md-header__topic wrapper with version selector inside
const html = renderVersionSelector(versions, currentVersion);
const topicWrapper = document.createElement('div');
topicWrapper.className = 'md-header__topic';
topicWrapper.innerHTML = html;

// Append to .md-header
header.appendChild(topicWrapper);
})
.catch(error => {
console.error('[Version Selector] Failed to load:', error.message);
});
}

// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initVersionSelector);
} else {
initVersionSelector();
}
})();
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ extra_javascript:
- '//w3.siemens.com/ote/ote_config.js'
- '//w3.siemens.com/ote/sinet/ote.js'
- 'https://assets.adobedtm.com/5dfc7d97c6fb/f16b45bec907/launch-af252bb19983.min.js'
- '_src/version-selector.js'
extra:
links:
- name: 'GitHub'
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "MIT"
requires-python = ">=3.11, <4"
readme = "README.md"
dependencies = [
"mkdocs-code-siemens-code-docs-theme>=7.7.0,<8",
"mkdocs-code-siemens-code-docs-theme>=7.8.0,<8",
"mkdocs-minify-html-plugin==0.3.9",
"mkdocs-element-docs-builder",
]
Expand Down
Loading
Loading