diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..9f732a1 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,154 @@ +# Copilot Instructions for Killed by Scrum.org + +This document contains important instructions for GitHub Copilot when working with this project. + +## Project Overview + +This is a Hugo-based static website for the "Killed by Scrum.org" project, which tracks obituaries of discontinued Scrum.org initiatives, tools, and programmes. + +## Build Commands + +### Development Server + +To run the Hugo development server: + +```powershell +hugo serve --source site --config hugo.yaml,hugo.local.yaml +``` + +### Production Build + +To build the site for production: + +```powershell +hugo --source site --config hugo.yaml +``` + +## Project Structure + +- `site/` - Hugo source code + - `hugo.yaml` - Main Hugo configuration + - `hugo.local.yaml` - Local development configuration (if exists) + - `content/` - Markdown content files + - `data/register.json` - Obituary data + - `layouts/` - Hugo templates + - `static/` - Static assets +- `public/` - Generated Hugo output (after build) +- `.powershell/` - PowerShell automation scripts + +## Key Features + +### GitHub Discussions Integration + +- **Automated Discussion Creation**: PowerShell script `.powershell/Create-GitHubDiscussions.ps1` creates GitHub discussions +- **Data Integration**: Script adds `discussionId` and `discussionUrl` fields to obituaries in `site/data/register.json` +- **Template Integration**: Hugo templates read these fields directly to display discussion buttons +- **Category Management**: Creates discussions in the "Obituary" category +- **Idempotent Operation**: Safe to run multiple times, skips existing discussions + +### Configuration + +- GitHub repository is configured in `site.Params.githubRepo` (hugo.yaml) +- Currently set to: `https://github.com/MrHinsh/killed-by-scrumorg` + +## Development Notes + +### GitHub Discussions Workflow + +**Prerequisites:** + +1. Set `HUGO_GITHUB_TOKEN` environment variable with a GitHub Personal Access Token +2. Token must have `public_repo` or `repo` scope (not just `write:discussion`) +3. GitHub Discussions must be enabled on the repository +4. "Obituary" category must exist in GitHub Discussions + +**Running the Script:** + +```powershell +.\.powershell\Create-GitHubDiscussions.ps1 +``` + +**How it works:** + +1. The script scans `site/data/register.json` for obituaries without `discussionId` fields +2. Creates GitHub discussions for those obituaries in the "Obituary" category +3. Updates the JSON file with `discussionId` and `discussionUrl` fields +4. Hugo templates automatically display discussion buttons for linked obituaries +5. Script is idempotent - skips obituaries that already have discussion IDs + +**Integration with Hugo:** + +- Hugo templates check for `{{ if .discussionUrl }}` to conditionally show discussion buttons +- No Hugo functions or build-time API calls required +- All data is pre-populated in the JSON file by the PowerShell script + +### Data Format + +- Obituaries are stored in `site/data/register.json` +- Each obituary should have at least a `title`, `description`, `birth_date`, and `death_date` field +- Optional fields: `type`, `url` +- After running the PowerShell script, obituaries will have `discussionId` and `discussionUrl` fields + +## Common Tasks + +### Adding New Obituaries + +1. Edit `site/data/register.json` +2. Add new obituary object with required fields (`title`, `description`, `birth_date`, `death_date`) +3. Run the PowerShell script to create discussions: `.\.powershell\Create-GitHubDiscussions.ps1` +4. Build the site with Hugo + +### Creating GitHub Discussions + +The PowerShell script automatically handles this: + +```powershell +# Using environment variable (recommended) +.\.powershell\Create-GitHubDiscussions.ps1 + +# Or passing token directly +.\.powershell\Create-GitHubDiscussions.ps1 -GitHubToken "your-github-token" +``` + +### Testing Changes + +1. Use `hugo serve --source site --config hugo.yaml,hugo.local.yaml` for live reload +2. Access the site at `http://localhost:1313` + +### Deployment + +- The site uses Azure Static Web Apps +- Configuration files: `staticwebapp.config.*.json` +- Different configs for different environments (production, preview, canary) + +## File Locations + +### Hugo Templates + +- Main page: `site/layouts/index.html` +- Base template: `site/layouts/_default/baseof.html` +- **Note**: No discussion function needed - discussions are handled via data fields + +### Styling + +- CSS: `site/static/css/style.css` +- Images: `site/static/images/` + +### Content + +- Homepage: `site/content/_index.md` +- Data: `site/data/register.json` + +### PowerShell Scripts + +- GitHub Discussions: `.powershell/Create-GitHubDiscussions.ps1` + +## Important Notes + +- Always use the correct Hugo command with `--source site` flag +- The project has multiple configuration files - use both `hugo.yaml` and `hugo.local.yaml` for development +- GitHub discussions are created via PowerShell script, not during Hugo build +- The site is statically generated - discussion data is pre-populated in JSON +- PowerShell script is idempotent and safe to run multiple times +- **Token Requirements**: GitHub token needs `public_repo` or `repo` scope, not just `write:discussion` +- **No backup files**: PowerShell script doesn't create backup files to avoid Hugo build conflicts diff --git a/.powershell/Create-GitHubDiscussions.ps1 b/.powershell/Create-GitHubDiscussions.ps1 new file mode 100644 index 0000000..97de973 --- /dev/null +++ b/.powershell/Create-GitHubDiscussions.ps1 @@ -0,0 +1,291 @@ +# Create-GitHubDiscussions.ps1 +# This script creates GitHub discussions for obituaries and updates the register.json file with discussion IDs + +param( + [Parameter(Mandatory = $false)] + [string]$GitHubToken = $env:HUGO_GITHUB_TOKEN, + + [Parameter(Mandatory = $false)] + [string]$SiteDataPath = "site/data/register.json", + + [Parameter(Mandatory = $false)] + [string]$RepoOwner = "MrHinsh", + + [Parameter(Mandatory = $false)] + [string]$RepoName = "killed-by-scrumorg", + + [Parameter(Mandatory = $false)] + [string]$Category = "Obituary" +) + +$ErrorActionPreference = 'Stop' + +# Set script location as working directory +$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoRoot = Split-Path -Parent $scriptPath +Set-Location $repoRoot + +Write-Host "Creating GitHub Discussions for obituaries..." -ForegroundColor Green +Write-Host "Repository: $RepoOwner/$RepoName" -ForegroundColor Cyan +Write-Host "Data file: $SiteDataPath" -ForegroundColor Cyan + +# Validate parameters +if (-not $GitHubToken) { + Write-Error "GitHub token not provided. Set HUGO_GITHUB_TOKEN environment variable or pass -GitHubToken parameter." + exit 1 +} + +# Read the obituaries data +$dataFile = Join-Path $repoRoot $SiteDataPath +if (-not (Test-Path $dataFile)) { + Write-Error "Data file not found: $dataFile" + exit 1 +} + +Write-Host "Reading obituaries from: $dataFile" -ForegroundColor Yellow + +try { + $obituariesData = Get-Content $dataFile -Raw | ConvertFrom-Json + Write-Host "Found $($obituariesData.Count) obituaries to process" -ForegroundColor Green +} +catch { + Write-Error "Failed to parse JSON from $dataFile`: $($_.Exception.Message)" + exit 1 +} + +# GitHub API headers +$headers = @{ + 'Authorization' = "Bearer $GitHubToken" + 'Accept' = 'application/vnd.github.v3+json' + 'Content-Type' = 'application/json' + 'User-Agent' = 'KilledByScrum-DiscussionCreator/1.0' +} + +# Function to get repository ID +function Get-RepositoryId { + param($repoOwner, $repoName) + + $repoUrl = "https://api.github.com/repos/$repoOwner/$repoName" + + try { + $response = Invoke-RestMethod -Uri $repoUrl -Headers $headers -Method GET + return $response.node_id + } + catch { + Write-Warning "Failed to get repository ID: $($_.Exception.Message)" + return $null + } +} + +# Function to get discussion categories using GraphQL +function Get-DiscussionCategories { + param($repoOwner, $repoName) + + $graphqlQuery = @{ + query = "query { repository(owner: `"$repoOwner`", name: `"$repoName`") { discussionCategories(first: 20) { nodes { id name slug } } } }" + } + + $graphqlHeaders = @{ + 'Authorization' = "Bearer $GitHubToken" + 'Content-Type' = 'application/json' + 'User-Agent' = 'KilledByScrum-DiscussionCreator/1.0' + } + + try { + $response = Invoke-RestMethod -Uri "https://api.github.com/graphql" -Headers $graphqlHeaders -Method POST -Body ($graphqlQuery | ConvertTo-Json) + + if ($response.errors) { + Write-Warning "GraphQL errors getting categories: $($response.errors | ConvertTo-Json)" + return @() + } + + if ($response.data.repository.discussionCategories.nodes) { + return $response.data.repository.discussionCategories.nodes + } + + return @() + } + catch { + Write-Warning "Failed to get discussion categories: $($_.Exception.Message)" + return @() + } +} + +# Function to search for existing discussion +function Find-ExistingDiscussion { + param($title, $repoOwner, $repoName) + + $searchQuery = "repo:$repoOwner/$repoName in:title `"$title`" type:discussion" + $searchUrl = "https://api.github.com/search/issues?q=$([System.Uri]::EscapeDataString($searchQuery))" + + try { + $response = Invoke-RestMethod -Uri $searchUrl -Headers $headers -Method GET + if ($response.total_count -gt 0) { + return $response.items[0] + } + return $null + } + catch { + Write-Warning "Failed to search for discussion '$title': $($_.Exception.Message)" + return $null + } +} + +# Function to create discussion using GraphQL +function New-GitHubDiscussion { + param($title, $body, $repositoryId, $categoryId) + + $mutation = @{ + query = "mutation(`$repositoryId: ID!, `$categoryId: ID!, `$title: String!, `$body: String!) { createDiscussion(input: {repositoryId: `$repositoryId, categoryId: `$categoryId, title: `$title, body: `$body}) { discussion { id url number } } }" + variables = @{ + repositoryId = $repositoryId + categoryId = $categoryId + title = $title + body = $body + } + } + + $graphqlHeaders = @{ + 'Authorization' = "Bearer $GitHubToken" + 'Content-Type' = 'application/json' + 'User-Agent' = 'KilledByScrum-DiscussionCreator/1.0' + } + + try { + $response = Invoke-RestMethod -Uri "https://api.github.com/graphql" -Headers $graphqlHeaders -Method POST -Body ($mutation | ConvertTo-Json -Depth 10) + + if ($response.errors) { + Write-Warning "GraphQL errors: $($response.errors | ConvertTo-Json)" + return $null + } + + if ($response.data.createDiscussion) { + return $response.data.createDiscussion.discussion + } + + return $null + } + catch { + Write-Warning "Failed to create discussion '$title': $($_.Exception.Message)" + return $null + } +} + +# Get repository ID +Write-Host "Getting repository information..." -ForegroundColor Yellow +$repositoryId = Get-RepositoryId $RepoOwner $RepoName +if (-not $repositoryId) { + Write-Error "Failed to get repository ID for $RepoOwner/$RepoName" + exit 1 +} +Write-Host "Repository ID: $repositoryId" -ForegroundColor Green + +# Get discussion categories +Write-Host "Getting discussion categories..." -ForegroundColor Yellow +$categories = Get-DiscussionCategories $RepoOwner $RepoName +$obituaryCategory = $categories | Where-Object { $_.slug -eq $Category.ToLower() } + +if (-not $obituaryCategory) { + Write-Warning "Category '$Category' not found. Available categories:" + $categories | ForEach-Object { Write-Host " - $($_.name) ($($_.slug))" -ForegroundColor Gray } + + # Try to find a suitable fallback category + $generalCategory = $categories | Where-Object { $_.slug -eq "general" -or $_.name -eq "General" } + if ($generalCategory) { + Write-Host "Using 'General' category as fallback" -ForegroundColor Yellow + $obituaryCategory = $generalCategory + } + else { + Write-Error "No suitable category found. Please create an 'obituary' category in GitHub Discussions." + exit 1 + } +} + +Write-Host "Using category: $($obituaryCategory.name) (ID: $($obituaryCategory.id))" -ForegroundColor Green + +# Track changes +$dataChanged = $false +$createdCount = 0 +$skippedCount = 0 +$errorCount = 0 + +# Process each obituary +foreach ($obituary in $obituariesData) { + $title = $obituary.title + Write-Host "`nProcessing: $title" -ForegroundColor Yellow + + # Check if discussion ID already exists + if ($obituary.PSObject.Properties['discussionId'] -and $obituary.discussionId) { + Write-Host " → Discussion ID already exists: $($obituary.discussionId)" -ForegroundColor Gray + $skippedCount++ + continue + } + + # Search for existing discussion + $existingDiscussion = Find-ExistingDiscussion $title $RepoOwner $RepoName + if ($existingDiscussion) { + Write-Host " → Found existing discussion: $($existingDiscussion.html_url)" -ForegroundColor Green + $obituary | Add-Member -NotePropertyName 'discussionId' -NotePropertyValue $existingDiscussion.number -Force + $dataChanged = $true + $skippedCount++ + } + else { + Write-Host " → Creating new discussion..." -ForegroundColor Cyan + + # Create discussion body + $body = @" +This is a discussion thread for the obituary: **$title** + +$($obituary.description) + +[View full obituary](https://killedbyscrumorg.com) + +--- + +*This discussion was automatically generated for the Killed by Scrum.org project.* +"@ + # Create the discussion + $newDiscussion = New-GitHubDiscussion $title $body $repositoryId $obituaryCategory.id + if ($newDiscussion) { + Write-Host " ✓ Created discussion: $($newDiscussion.url)" -ForegroundColor Green + $obituary | Add-Member -NotePropertyName 'discussionId' -NotePropertyValue $newDiscussion.number -Force + $dataChanged = $true + $createdCount++ + + # Add a small delay to avoid rate limiting + Start-Sleep -Milliseconds 500 + } + else { + Write-Host " ✗ Failed to create discussion" -ForegroundColor Red + $errorCount++ + } + } +} + +# Save updated data if changes were made +if ($dataChanged) { + Write-Host "`nSaving updated data to $dataFile..." -ForegroundColor Yellow + try { + # Save updated data with proper formatting (no backup to avoid Hugo conflicts) + $obituariesData | ConvertTo-Json -Depth 10 | Set-Content $dataFile -Encoding UTF8 + Write-Host "Data file updated successfully!" -ForegroundColor Green + } + catch { + Write-Error "Failed to save updated data: $($_.Exception.Message)" + exit 1 + } +} + +# Summary +Write-Host "`nSummary:" -ForegroundColor Green +Write-Host " Created: $createdCount discussions" -ForegroundColor Green +Write-Host " Skipped: $skippedCount discussions (already exist or have IDs)" -ForegroundColor Yellow +Write-Host " Errors: $errorCount discussions" -ForegroundColor Red +Write-Host " Total processed: $($obituariesData.Count) obituaries" -ForegroundColor Cyan + +if ($dataChanged) { + Write-Host "`nThe register.json file has been updated with discussion IDs." -ForegroundColor Green + Write-Host "You can now rebuild your Hugo site to see the discussion links." -ForegroundColor Yellow +} + +Write-Host "`nDone!" -ForegroundColor Green diff --git a/readme.md b/readme.md index 95e6247..e9b3c58 100644 --- a/readme.md +++ b/readme.md @@ -23,12 +23,13 @@ If you are not familiar with or do not want to use `git`, submit a [new issue](h 1. Fork this repository. 2. Create a new branch named after the initiative you're adding. 3. Open the `site\data\register.json` file and add the new entry manually. -4. Run `hugo serve --source site --config hugo.yaml,hugo.local.yaml` to ensure the file is properly formatted. -5. Commit your changes and open a Pull Request (PR) using the new branch. +4. Commit your changes and open a Pull Request (PR) using the new branch. + +**Note**: GitHub discussions will be automatically created for new obituaries during the next deployment. For contributions beyond `site\data\register.json`, see the [Contributing Guide](.github/CONTRIBUTING.md). -### Environments +## Environments - [Production](https://lemon-stone-0045b7f10.6.azurestaticapps.net) - [Preview](https://lemon-stone-0045b7f10-preview.centralus.6.azurestaticapps.net/) @@ -36,7 +37,7 @@ For contributions beyond `site\data\register.json`, see the [Contributing Guide] Pull Requests automatically spawn environments. However, PR's from forks require approval to run. -### Editorial Guidelines +## Editorial Guidelines #### Description @@ -55,3 +56,65 @@ Link to a source confirming its existence and end-of-life, ideally from Scrum.or --- Help us document the evolution of the Scrum.org ecosystem by capturing its discontinued efforts with clarity and respect. + +## Technical Details + +### Features + +- **Interactive Obituaries**: Each discontinued initiative is displayed with tombstone or guillotine imagery +- **GitHub Discussions**: Community discussions are automatically created for each obituary +- **Hugo-Powered**: Fast, static site generation with responsive design +- **Automated Workflow**: PowerShell script manages GitHub discussions integration + +### Contributing (Technical) + +For technical contributors working on the site itself: + +1. Fork this repository +2. Make your changes to the appropriate files +3. Test locally using the development workflow below +4. Submit a Pull Request + +See the [Contributing Guide](.github/CONTRIBUTING.md) for detailed guidelines. + +### Building and Development + +#### Development Server + +```powershell +hugo serve --source site --config hugo.yaml,hugo.local.yaml +``` + +#### Production Build + +```powershell +hugo --source site --config hugo.yaml +``` + +#### Testing Changes + +Run the development server and access the site at `http://localhost:1313` for live reload during development. + +### GitHub Discussions Integration (PowerShell) + +This site automatically creates GitHub discussions for each obituary. The integration is managed via a PowerShell script: + +```powershell +.\.powershell\Create-GitHubDiscussions.ps1 +``` + +**Requirements:** + +- Set the `HUGO_GITHUB_TOKEN` environment variable with a GitHub Personal Access Token +- Token needs `public_repo` or `repo` scope (not just `write:discussion`) +- GitHub Discussions must be enabled on the repository +- An "Obituary" category must exist in GitHub Discussions + +**How it works:** + +1. Scans `site/data/register.json` for obituaries without `discussionId` +2. Creates GitHub discussions in the "Obituary" category +3. Updates the JSON file with `discussionId` and `discussionUrl` fields +4. Hugo templates automatically display discussion buttons for linked obituaries + +The script is idempotent - you can run it multiple times safely. It will skip obituaries that already have discussions. diff --git a/site/data/register.json b/site/data/register.json index a49b412..174f3ee 100644 --- a/site/data/register.json +++ b/site/data/register.json @@ -5,7 +5,8 @@ "birth_date": "2012-05-01", "death_date": "2024-02-01", "type": "program", - "url": "https://www.scrum.org/resources/scrum-studio-model-innovation" + "url": "https://www.scrum.org/resources/scrum-studio-model-innovation", + "discussionId": 10 }, { "title": "The Agility Guide to Evidence-Based Change", @@ -13,7 +14,8 @@ "birth_date": "2014-05-01", "death_date": "2018-02-01", "type": "program", - "url": "https://www.scrum.org/resources/agility-guide-evidence-based-change" + "url": "https://www.scrum.org/resources/agility-guide-evidence-based-change", + "discussionId": 11 }, { "title": "Agility Path", @@ -21,7 +23,8 @@ "birth_date": "2013-07-01", "death_date": "2017-12-01", "type": "software", - "url": "https://www.scrum.org/agility-path" + "url": "https://www.scrum.org/agility-path", + "discussionId": 12 }, { "title": "Evidence-Based Management Consultant", @@ -29,7 +32,8 @@ "birth_date": "2013-03-01", "death_date": "2019-09-01", "type": "program", - "url": "https://www.scrum.org/resources/evidence-based-management" + "url": "https://www.scrum.org/resources/evidence-based-management", + "discussionId": 13 }, { "title": "Scrum Development Kit (SDK)", @@ -37,7 +41,8 @@ "birth_date": "2015-10-01", "death_date": "2016-07-15", "type": "content", - "url": "https://kenschwaber.wordpress.com/2015/11/22/scrum-development-kit/" + "url": "https://kenschwaber.wordpress.com/2015/11/22/scrum-development-kit/", + "discussionId": 14 }, { "title": "Partners in Principle", @@ -45,14 +50,16 @@ "birth_date": "2014-01-01", "death_date": "2019-11-01", "type": "program", - "url": "https://www.scrum.org/resources/blog/professional-training-network" + "url": "https://www.scrum.org/resources/blog/professional-training-network", + "discussionId": 15 }, { "title": "Professional Scrum Guided Learning Platform (GLP)", "description": "The Guided Learning Platform is a Scrum.org platform that provides structured learning paths and resources for Scrum practitioners.", "death_date": "2025-03-01", "birth_date": "2021-01-01", - "url": "https://www.scrum.org/guided-learning/professional-scrum-guided-learning-platform" + "url": "https://www.scrum.org/guided-learning/professional-scrum-guided-learning-platform", + "discussionId": 16 }, { "title": "Advanced Professional Scrum Master (APSM)", @@ -60,7 +67,8 @@ "death_date": "2020-01-01", "birth_date": "2019-01-01", "type": "program", - "url": "https://www.scrum.org/" + "url": "https://www.scrum.org/", + "discussionId": 17 }, { "title": "Professional Scrum Expert", @@ -68,7 +76,8 @@ "death_date": "2015-01-01", "birth_date": "2015-01-01", "type": "certification", - "url": "https://s3.amazonaws.com/scrumorg-blog/wp-content/uploads/2015/01/22191504/Announcement-The-Scrum.org-Professional-Series-Updates-2015.pdf" + "url": "https://s3.amazonaws.com/scrumorg-blog/wp-content/uploads/2015/01/22191504/Announcement-The-Scrum.org-Professional-Series-Updates-2015.pdf", + "discussionId": 18 }, { "title": "Professional Scrum Coaching", @@ -76,6 +85,7 @@ "death_date": "2021-07-01", "birth_date": "2021-12-01", "type": "initiative", - "url": "https://s3.amazonaws.com/scrumorg-blog/wp-content/uploads/2015/01/22191504/Announcement-The-Scrum.org-Professional-Series-Updates-2015.pdf" + "url": "https://s3.amazonaws.com/scrumorg-blog/wp-content/uploads/2015/01/22191504/Announcement-The-Scrum.org-Professional-Series-Updates-2015.pdf", + "discussionId": 19 } ] diff --git a/site/hugo.yaml b/site/hugo.yaml index 534c231..3eed963 100644 --- a/site/hugo.yaml +++ b/site/hugo.yaml @@ -16,7 +16,9 @@ params: description: "Killed by Scrum.org" keywords: "Scrum" og_image: "/images/og-image.jpg" # URL to the default Open Graph image - editURL: "https://github.com/nkdAgility/KilledByScrumorg/edit/main/site/content/" # URL to edit content on GitHub + githubRepo: "https://github.com/MrHinsh/killed-by-scrumorg" + editURL: "https://github.com/MrHinsh/killed-by-scrumorg/edit/main/site/content/" # URL to edit content on GitHub + addObituaryUrl: "https://github.com/MrHinsh/killed-by-scrumorg/issues/new?template=add-an-obituary.yml" # Enable sitemap and robots.txt sitemap: diff --git a/site/layouts/_default/baseof.html b/site/layouts/_default/baseof.html index fb3438a..4878b6f 100644 --- a/site/layouts/_default/baseof.html +++ b/site/layouts/_default/baseof.html @@ -13,6 +13,7 @@ +
@@ -35,7 +36,7 @@