Skip to content
Merged
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
27 changes: 27 additions & 0 deletions .github/workflows/ghbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,39 @@ jobs:
if [ -f "dak.json" ]; then
echo "✅ Found dak.json - DAK processing will be enabled"
echo "DAK_ENABLED=true" >> $GITHUB_ENV

# Set repository context for DAK URL generation
echo "GITHUB_REPOSITORY=${{ github.repository }}" >> $GITHUB_ENV
echo "GITHUB_REF_NAME=${{ github.head_ref || github.ref_name }}" >> $GITHUB_ENV
echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV

else
echo "⚠️ do_dak=true but no dak.json found in repository root"
echo "DAK processing will be skipped"
echo "DAK_ENABLED=false" >> $GITHUB_ENV
fi

- name: DAK Preprocessing - Regenerate DAK configuration with current context
if: (inputs.do_dak != 'false') && (env.DAK_ENABLED == 'true')
run: |
echo "Regenerating DAK configuration with current branch context..."

# Check if DAK generation script exists locally, download if needed
if [ ! -f "input/scripts/generate_dak_from_sushi.py" ]; then
echo "DAK generation script not found locally, downloading from smart-base repository..."
mkdir -p input/scripts
curl -L -f -o "input/scripts/generate_dak_from_sushi.py" "https://raw.githubusercontent.com/WorldHealthOrganization/smart-base/main/input/scripts/generate_dak_from_sushi.py" 2>/dev/null || echo "Failed to download generate_dak_from_sushi.py"
curl -L -f -o "input/scripts/dak_url_utils.py" "https://raw.githubusercontent.com/WorldHealthOrganization/smart-base/main/input/scripts/dak_url_utils.py" 2>/dev/null || echo "Failed to download dak_url_utils.py"
fi

# Regenerate DAK configuration with current environment context
if [ -f "input/scripts/generate_dak_from_sushi.py" ]; then
python3 input/scripts/generate_dak_from_sushi.py
echo "✅ DAK configuration regenerated with current context"
else
echo "⚠️ DAK generation script not available, using existing dak.json"
fi

- name: DAK Preprocessing - Generate DMN Questionnaires
if: (inputs.do_dak != 'false') && (env.DAK_ENABLED == 'true')
run: |
Expand Down
4 changes: 3 additions & 1 deletion dak.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"description": "Base SMART Guidelines implementation guide to be used as the base dependency for all SMART Guidelines IGs",
"version": "0.2.0",
"status": "draft",
"publicationUrl": "http://smart.who.int/base",
"publicationUrl": "https://smart.who.int/base",
"previewUrl": "https://WorldHealthOrganization.github.io/smart-base",
"canonicalUrl": "https://smart.who.int/base",
"license": "CC-BY-SA-3.0-IGO",
"copyrightYear": "2023+",
"publisher": {
Expand Down
4 changes: 3 additions & 1 deletion input/fsh/models/DAK.fsh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ Description: "Logical Model for representing a complete Digital Adaptation Kit (
* description[x] 1..1 string or uri "DAK Description" "Description of the DAK - either Markdown content or a URI to a Markdown file (absolute or relative to repository root)"
* version 1..1 string "DAK Version" "Version of the DAK"
* status 1..1 code "DAK Status" "Publication status of the DAK"
* publicationUrl 1..1 url "Publication URL" "Canonical URL for the DAK (e.g., http://smart.who.int/base)"
* publicationUrl 1..1 url "Publication URL" "Canonical URL for the published DAK (e.g., https://smart.who.int/base for WHO repositories)"
* previewUrl 1..1 url "Preview URL" "Preview URL for the current CI build (e.g., https://worldhealthorganization.github.io/smart-base)"
* canonicalUrl 1..1 url "Canonical URL" "The canonical URL to use for this DAK instance - equals publicationUrl for release branches, previewUrl for development branches"
* license 1..1 code "License" "License under which the DAK is published"
* copyrightYear 1..1 string "Copyright Year" "Year or year range for copyright"

Expand Down
17 changes: 17 additions & 0 deletions input/pagecontent/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ This implementation guide contains base conformance resources for use in all WHO

See the [SMART IG Starter Kit](https://smart.who.int/ig-starter-kit/) for more information on building and using WHO SMART Guidelines.

### DAK (Digital Adaptation Kit) URL Handling

For repositories that contain a `dak.json` file in the root directory, this implementation guide provides enhanced URL handling for publication and preview scenarios:

#### Publication URLs
- **WHO Repositories**: For repositories owned by `WorldHealthOrganization`, the publication URL follows the pattern `https://smart.who.int/{stub}` where `{stub}` is the repository name with any `smart-` prefix removed.
- **Other Repositories**: Use the canonical URL specified in `sushi-config.yaml` or fall back to GitHub Pages pattern.

#### Preview URLs
- **All Repositories**: Preview URLs use the GitHub Pages pattern `https://{profile}.github.io/{repo}` for current CI builds.

#### Branch-Based URL Selection
- **Release Branches** (prefixed with `release-`): Use publication URLs for canonical references and resource identifiers.
- **Development Branches**: Use preview URLs for canonical references and resource identifiers.

The DAK configuration is automatically regenerated during CI builds to ensure URLs are appropriate for the current branch context.

### Dependencies

{% include dependency-table-short.xhtml %}
Expand Down
128 changes: 128 additions & 0 deletions input/scripts/dak_url_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
DAK URL Utilities

Shared utilities for generating publication and preview URLs for DAK-enabled repositories.
These utilities are used by PR comment scripts and other build tools to determine
the appropriate URLs based on repository configuration and branch context.
"""

import json
import os
from pathlib import Path
from typing import Dict, Any, Optional, Tuple


def load_dak_config(dak_path: Path = None) -> Optional[Dict[str, Any]]:
"""Load dak.json configuration if it exists."""
if dak_path is None:
dak_path = Path("dak.json")

if not dak_path.exists():
return None

try:
with open(dak_path, 'r', encoding='utf-8') as file:
return json.load(file)
except (json.JSONDecodeError, IOError):
return None


def generate_dak_publication_url(repo_name: str, canonical_url: str = "") -> str:
"""Generate publication URL based on repository ownership and name."""
# Check if this is a WorldHealthOrganization repository
github_repo = os.getenv('GITHUB_REPOSITORY', '')
if github_repo.startswith('WorldHealthOrganization/'):
# Extract stub by removing 'smart-' prefix if present
stub = repo_name
if stub.startswith('smart-'):
stub = stub[6:] # Remove 'smart-' prefix
return f"https://smart.who.int/{stub}"
else:
# For non-WHO repositories, use canonical URL or default pattern
if canonical_url:
return canonical_url
# Fallback to GitHub Pages pattern
if github_repo:
profile, repo = github_repo.split('/')
return f"https://{profile}.github.io/{repo}"
return canonical_url or ""


def generate_dak_preview_url(repo_name: str = "") -> str:
"""Generate preview URL for current CI build."""
github_repo = os.getenv('GITHUB_REPOSITORY', '')
if github_repo:
profile, repo = github_repo.split('/')
return f"https://{profile}.github.io/{repo}"
# Fallback for local development
return f"https://worldhealthorganization.github.io/{repo_name}"


def is_release_branch() -> bool:
"""Check if current branch is a release branch (prefixed with 'release-')."""
branch_name = os.getenv('GITHUB_REF_NAME', os.getenv('BRANCH_NAME', ''))
return branch_name.startswith('release-')


def get_deployment_urls(branch: str, repository: str = "") -> Tuple[str, str]:
"""
Get appropriate deployment URLs based on DAK configuration and branch context.

Returns:
Tuple[str, str]: (deployment_url, base_url) where:
- deployment_url: The URL for the specific branch deployment
- base_url: The base URL for the repository
"""
# Load DAK configuration if available
dak_config = load_dak_config()

if dak_config:
# Use DAK-specific URL logic
repo_name = repository.split('/')[-1] if repository else ""

if is_release_branch():
# For release branches, use publication URL as base
base_url = dak_config.get('publicationUrl', generate_dak_publication_url(repo_name))
else:
# For non-release branches, use preview URL as base
base_url = dak_config.get('previewUrl', generate_dak_preview_url(repo_name))

# Generate branch-specific URL
if branch == 'main':
deployment_url = base_url
else:
# Extract branch suffix for URL
branch_for_url = branch.split('/')[-1] if '/' in branch else branch
deployment_url = f"{base_url.rstrip('/')}/branches/{branch_for_url}"
else:
# Fallback to GitHub Pages pattern for non-DAK repositories
github_repo = repository or os.getenv('GITHUB_REPOSITORY', '')
if github_repo:
profile, repo = github_repo.split('/')
base_url = f"https://{profile}.github.io/{repo}"
else:
base_url = "https://worldhealthorganization.github.io/smart-base"
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded fallback URL should be extracted to a constant or made configurable to avoid magic strings in the code.

Copilot uses AI. Check for mistakes.

if branch == 'main':
deployment_url = f"{base_url}/"
else:
branch_for_url = branch.split('/')[-1] if '/' in branch else branch
deployment_url = f"{base_url}/branches/{branch_for_url}/"

return deployment_url, base_url


def get_canonical_url_for_branch(branch: str, repository: str = "") -> str:
"""Get the canonical URL that should be used for the given branch."""
dak_config = load_dak_config()

if dak_config:
if is_release_branch():
return dak_config.get('publicationUrl', dak_config.get('canonicalUrl', ''))
else:
return dak_config.get('previewUrl', dak_config.get('canonicalUrl', ''))
else:
# Fallback for non-DAK repositories
deployment_url, _ = get_deployment_urls(branch, repository)
return deployment_url
65 changes: 63 additions & 2 deletions input/scripts/generate_dak_from_sushi.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""

import json
import os
import sys
import yaml
from pathlib import Path
Expand Down Expand Up @@ -48,20 +49,77 @@ def convert_publisher(sushi_publisher: Any) -> Dict[str, str]:
return {"name": ""}


def generate_publication_url(repo_name: str, canonical_url: str) -> str:
"""Generate publication URL based on repository ownership and name."""
# Check if this is a WorldHealthOrganization repository
if os.getenv('GITHUB_REPOSITORY', '').startswith('WorldHealthOrganization/'):
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded organization name 'WorldHealthOrganization/' should be extracted to a constant or configuration to improve maintainability and make it easier to adapt for other organizations.

Copilot uses AI. Check for mistakes.
# Extract stub by removing 'smart-' prefix if present
stub = repo_name
if stub.startswith('smart-'):
stub = stub[6:] # Remove 'smart-' prefix
return f"https://smart.who.int/{stub}"
else:
# For non-WHO repositories, use canonical URL or default pattern
if canonical_url:
return canonical_url
# Fallback to GitHub Pages pattern
github_repo = os.getenv('GITHUB_REPOSITORY', '')
if github_repo:
return f"https://{github_repo.split('/')[0]}.github.io/{github_repo.split('/')[1]}"
return canonical_url or ""


def generate_preview_url(repo_name: str) -> str:
"""Generate preview URL for current CI build."""
github_repo = os.getenv('GITHUB_REPOSITORY', '')
if github_repo:
profile, repo = github_repo.split('/')
return f"https://{profile}.github.io/{repo}"
# Fallback for local development
return f"https://worldhealthorganization.github.io/{repo_name}"


def is_release_branch() -> bool:
"""Check if current branch is a release branch (prefixed with 'release-')."""
branch_name = os.getenv('GITHUB_REF_NAME', os.getenv('BRANCH_NAME', ''))
return branch_name.startswith('release-')


def generate_dak_json(sushi_config: Dict[str, Any]) -> Dict[str, Any]:
"""Generate dak.json structure from sushi-config.yaml."""

# Extract repository information
repo_id = sushi_config.get("id", "")
repo_name = repo_id.split('.')[-1] if '.' in repo_id else repo_id
canonical_url = sushi_config.get("canonical", "")

# Generate URLs based on branch type and repository ownership
if is_release_branch():
# For release branches, use publication URL for canonical references
publication_url = generate_publication_url(repo_name, canonical_url)
preview_url = generate_preview_url(repo_name)
# Use publication URL as canonical URL for release branches
effective_canonical = publication_url
else:
# For non-release branches, use preview URL
publication_url = generate_publication_url(repo_name, canonical_url)
preview_url = generate_preview_url(repo_name)
# Use preview URL as canonical URL for development branches
effective_canonical = preview_url

# Core DAK identity (mapped from sushi config)
dak = {
"resourceType": "DAK",
"resourceDefinition": "http://smart.who.int/base/StructureDefinition/DAK",
"id": sushi_config.get("id", ""),
"id": repo_id,
"name": sushi_config.get("name", ""),
"title": sushi_config.get("title", ""),
"description": sushi_config.get("description", ""),
"version": sushi_config.get("version", "0.1.0"),
"status": sushi_config.get("status", "draft"),
"publicationUrl": sushi_config.get("canonical", ""),
"publicationUrl": publication_url,
"previewUrl": preview_url,
"canonicalUrl": effective_canonical,
"license": sushi_config.get("license", "CC0-1.0"),
"copyrightYear": sushi_config.get("copyrightYear", str(datetime.now().year)),
"publisher": convert_publisher(sushi_config.get("publisher", {}))
Expand Down Expand Up @@ -103,6 +161,9 @@ def main():
print(f"DAK ID: {dak_config['id']}")
print(f"DAK Title: {dak_config['title']}")
print(f"Publication URL: {dak_config['publicationUrl']}")
print(f"Preview URL: {dak_config['previewUrl']}")
print(f"Canonical URL: {dak_config['canonicalUrl']}")
print(f"Is Release Branch: {is_release_branch()}")
except IOError as e:
print(f"Error writing output file: {e}")
sys.exit(1)
Expand Down
24 changes: 22 additions & 2 deletions input/scripts/pr_comment_finish.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
import requests
from urllib.parse import quote

# Try to import DAK URL utilities, fallback to inline implementation if not available
try:
from dak_url_utils import get_deployment_urls
DAK_UTILS_AVAILABLE = True
except ImportError:
DAK_UTILS_AVAILABLE = False


def sanitize_input(value: str) -> str:
"""Sanitize input to prevent injection attacks."""
Expand Down Expand Up @@ -67,16 +74,29 @@ def validate_job_status(status: str) -> str:
def generate_deployment_url(branch: str) -> str:
"""Generate deployment URL with proper sanitization."""
branch = sanitize_input(branch)
repository = os.getenv('GITHUB_REPOSITORY', 'WorldHealthOrganization/smart-base')

# Use DAK-aware URL generation if available
if DAK_UTILS_AVAILABLE:
try:
deployment_url, _ = get_deployment_urls(branch, repository)
return deployment_url
except Exception:
# Fallback to default implementation if DAK utils fail
pass

# Default GitHub Pages implementation
if branch == 'main':
return 'https://worldhealthorganization.github.io/smart-base/'
return f'https://{repository.split("/")[0].lower()}.github.io/{repository.split("/")[1]}/'
else:
# Extract branch suffix after last slash for URL
branch_for_url = branch.split('/')[-1] if '/' in branch else branch
branch_for_url = sanitize_input(branch_for_url)
# URL encode the branch name for safety
branch_encoded = quote(branch_for_url, safe='')
return f'https://worldhealthorganization.github.io/smart-base/branches/{branch_encoded}/'
profile = repository.split('/')[0].lower()
repo = repository.split('/')[1]
return f'https://{profile}.github.io/{repo}/branches/{branch_encoded}/'


def update_pr_comment(pr_number: int, repository: str, run_id: int, sha: str, branch: str, job_status: str, github_token: str):
Expand Down
Loading
Loading