diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 2b82407..a33952d 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -4,19 +4,80 @@ on: push: branches: ["main"] workflow_dispatch: + repository_dispatch: + types: [visibility_changed] permissions: contents: read pages: write id-token: write + deployments: write concurrency: group: "pages" cancel-in-progress: false jobs: + # ── Guard: check repo visibility ──────────────────────────────── + check-visibility: + runs-on: ubuntu-latest + outputs: + is_private: ${{ steps.check.outputs.is_private }} + steps: + - name: Check repo visibility + id: check + run: | + if [ "${{ github.event.repository.private }}" = "true" ]; then + echo "is_private=true" >> "$GITHUB_OUTPUT" + echo "::warning::Repository is private — Pages deployment will be skipped" + else + echo "is_private=false" >> "$GITHUB_OUTPUT" + fi + + # ── Teardown: remove Pages if repo is private ─────────────────── + teardown: + runs-on: ubuntu-latest + needs: check-visibility + if: needs.check-visibility.outputs.is_private == 'true' + steps: + - name: Remove GitHub Pages deployments + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + run: | + echo "Repository is private — removing existing Pages deployments" + + # List all deployments in the github-pages environment + DEPLOYMENTS=$(gh api "repos/${REPO}/deployments?environment=github-pages&per_page=100" --jq '.[].id' 2>/dev/null || true) + + if [ -z "$DEPLOYMENTS" ]; then + echo "No existing Pages deployments found" + exit 0 + fi + + # Mark each deployment as inactive, then delete it + for ID in $DEPLOYMENTS; do + echo "Deactivating deployment ${ID}..." + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + "repos/${REPO}/deployments/${ID}/statuses" \ + -f state=inactive 2>/dev/null || true + + echo "Deleting deployment ${ID}..." + gh api \ + --method DELETE \ + -H "Accept: application/vnd.github+json" \ + "repos/${REPO}/deployments/${ID}" 2>/dev/null || true + done + + echo "All Pages deployments removed" + + # ── Build (only if public) ────────────────────────────────────── build: runs-on: ubuntu-latest + needs: check-visibility + if: needs.check-visibility.outputs.is_private == 'false' steps: - name: Checkout uses: actions/checkout@v4 @@ -197,10 +258,6 @@ jobs: fi # ── Fix relative links for Jekyll ───────────────────────────── - # Jekyll with permalink:pretty changes paths, so relative links - # like [X](CONTRIBUTING.md) or [X](LICENSE) would 404. - # Point them at their Jekyll permalinks instead. - # Only targets known community files in root-level markdown. for f in *.md; do [ -f "$f" ] || continue [ -f "CONTRIBUTING.md" ] && sed -i 's|\](CONTRIBUTING\.md)|\](contributing/)|g' "$f" @@ -250,7 +307,6 @@ jobs: fi # ── Releases page ───────────────────────────────────────────── - # Write the JS to a separate file to avoid heredoc escaping issues mkdir -p _includes cat > _includes/releases.js << 'JSEOF' (async function() { @@ -262,6 +318,29 @@ jobs: var data = await res.json(); if (!data.length) { c.innerHTML = '
No releases found.
'; return; } + function inline(s) { + var codes = []; + s = s.replace(/`([^`]+)`/g, function(m, code) { + codes.push(code.replace(//g,'>')); + return '\x00CODE' + (codes.length - 1) + '\x00'; + }); + s = s + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') + .replace(/(https?:\/\/[^\s<)]+)/g, function(m, url, offset, str) { + if (str.charAt(offset - 1) === '"' || str.charAt(offset - 1) === '>' || str.substring(offset - 6, offset) === 'href="') return m; + return '' + url + ''; + }) + .replace(/(^|[\s(])@([a-zA-Z0-9_-]+)/g, function(m, pre, user) { + return pre + '@' + user + ''; + }) + .replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/(?$1'); + s = s.replace(/\x00CODE(\d+)\x00/g, function(m, idx) { + return '' + codes[parseInt(idx)] + '';
+ });
+ return s;
+ }
+
function md(s) {
if (!s) return '';
var lines = s.split('\n');
@@ -272,29 +351,29 @@ jobs:
var h3 = line.match(/^### (.+)$/);
var h2 = line.match(/^## (.+)$/);
var li = line.match(/^\* (.+)$/);
- var cb = line.match(/^```/);
+ var cb = line.match(/^```(.*)$/);
if (cb) {
if (inList) { html += ''; inList = false; }
+ var lang = cb[1].trim();
var code = '';
i++;
while (i < lines.length && !lines[i].match(/^```/)) { code += lines[i] + '\n'; i++; }
- html += '' + code + '';
+ var langLabel = lang ? '$1').replace(/\*\*([^*]+)\*\*/g, '$1').replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1');
- html += '' + text + '
'; + html += '' + inline(line) + '
'; } } } @@ -306,16 +385,52 @@ jobs: var d = new Date(r.published_at).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }); var latest = i === 0 ? 'latest' : ''; var openAttr = i === 0 ? ' open' : ''; + + // Commit SHA + var sha = r.target_commitish || ''; + var shortSha = sha.substring(0, 7); + var shaHtml = ''; + if (sha.length >= 7) { + shaHtml = 'Unable to load releases. Visit GitHub directly.
'; @@ -344,7 +459,6 @@ jobs: done if [ -n "$LICENSE_SRC" ]; then - # Create LICENSE.md with front matter + full license text if ! ([ -f "LICENSE.md" ] && head -1 LICENSE.md | grep -q '^\-\-\-'); then TEMP=$(mktemp) printf -- '---\nlayout: default\ntitle: License\npermalink: /license/\n---\n\n# License\n\n```\n' > "$TEMP" @@ -354,7 +468,6 @@ jobs: echo "Created LICENSE.md from ${LICENSE_SRC}" fi elif [ -f "LICENSE.md" ] && ! head -1 LICENSE.md | grep -q '^\-\-\-'; then - # LICENSE.md exists but has no front matter TEMP=$(mktemp) printf -- '---\nlayout: default\ntitle: License\npermalink: /license/\n---\n\n' > "$TEMP" cat LICENSE.md >> "$TEMP" @@ -397,12 +510,14 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v3 + # ── Deploy (only if build succeeded) ──────────────────────────── deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build + if: needs.build.result == 'success' steps: - name: Deploy to GitHub Pages id: deployment