Skip to content

Commit 8baaf9f

Browse files
committed
fix: index generation and add tests
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
1 parent 7cecc17 commit 8baaf9f

5 files changed

Lines changed: 275 additions & 30 deletions

File tree

.github/workflows/generate-top-index.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@ on:
44
push:
55
branches:
66
- master
7+
pull_request:
8+
branches:
9+
- master
10+
11+
permissions:
12+
contents: write
713

814
jobs:
915
build:
1016
name: Build index.html
1117
runs-on: ubuntu-latest
1218

1319
permissions:
14-
contents: write
20+
contents: read
1521

1622
steps:
1723
- name: Cache git metadata
@@ -31,7 +37,7 @@ jobs:
3137
- name: Get stable branches
3238
id: branch
3339
run: |
34-
branches=$(git ls-remote --heads origin "stable[0-9]*" | awk '{gsub(/^refs\/heads\/stable/, "", $2); print $2}' | sort -n -r | tr '\n' ' ')
40+
branches=$(git ls-remote --heads origin "heads/stable[0-9][0-9]" | awk '{gsub(/^refs\/heads\/stable/, "", $2); print $2}' | sort -n -r | tr '\n' ' ')
3541
echo "branches=$branches" >> $GITHUB_OUTPUT
3642
3743
- name: Setup php
@@ -43,14 +49,22 @@ jobs:
4349
mv build/index.html /tmp/index.html
4450
mv build/static/ /tmp/static/
4551
52+
- name: Verify index.html structure and links
53+
run: |
54+
# Temporarily put index.html in build folder for verification
55+
cp /tmp/index.html build/index.html
56+
php build/verify-index.php
57+
4658
- name: Switch to gh-pages branch
59+
if: github.event_name == 'push'
4760
run: |
4861
git fetch origin gh-pages
4962
git checkout gh-pages
5063
git config --local user.email "action@github.com"
5164
git config --local user.name "GitHub Action"
5265
5366
- name: Commit and push changes
67+
if: github.event_name == 'push'
5468
run: |
5569
# Move the generated index.html and static files to the root of the gh-pages branch
5670
rm -rf index.html static/

.github/workflows/sphinxbuild.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ permissions:
1212

1313
jobs:
1414
build:
15+
1516
name: Build ${{ matrix.manual.name }}
1617
runs-on: ubuntu-latest
1718

@@ -119,7 +120,7 @@ jobs:
119120
current_branch="${GITHUB_REF#refs/heads/}"
120121
121122
# Find the highest numbered stable branch from the remote
122-
highest_stable=$(git ls-remote --heads origin 'stable[0-9]*' | sed 's?.*refs/heads/stable??' | sort -n | tail -1)
123+
highest_stable=$(git ls-remote --heads origin "heads/stable[0-9][0-9]"' | sed 's?.*refs/heads/stable??' | sort -n | tail -1)
123124
highest_stable_branch="stable${highest_stable}"
124125
125126
echo "Current branch: $current_branch"

.github/workflows/update-workflow.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,22 @@ jobs:
5151
echo "Checking out file: ${{ inputs.file_path }} from master branch"
5252
git checkout origin/master -- ${{ inputs.file_path }}
5353
54+
- name: Adjust Python version for older branches
55+
run: |
56+
# Extract version from branch name (e.g., stable32 -> 32)
57+
VERSION=$(echo "${{ matrix.target_branch }}" | sed 's/stable//')
58+
59+
# For branches stable19-stable31, use Python 3.10 instead of 3.12
60+
if [ "$VERSION" -le 31 ]; then
61+
if grep -q "python-version: 3.12" "${{ inputs.file_path }}"; then
62+
sed -i 's/python-version: 3\.12/python-version: 3.10/g' "${{ inputs.file_path }}"
63+
echo "Updated Python version to 3.10 for stable$VERSION"
64+
fi
65+
fi
66+
5467
- name: Commit and push changes
5568
run: |
5669
git add ${{ inputs.file_path }}
57-
git commit -sm "chore: update ${{ inputs.file_path }} from master branch"
70+
git diff --cached --quiet || git commit -sm "chore: update ${{ inputs.file_path }} from master branch"
5871
5972
git push origin ${{ matrix.target_branch }}

build/build-index.php

Lines changed: 133 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,156 @@
11
<?php
22
require_once 'server-block.php';
33

4+
/**
5+
* Get the GitHub API headers with optional authentication
6+
*/
7+
function get_github_headers(): string {
8+
$headers = 'User-Agent: Nextcloud Documentation Builder';
9+
if ($token = getenv('GITHUB_TOKEN')) {
10+
$headers .= "\r\nAuthorization: token $token";
11+
}
12+
return $headers;
13+
}
14+
15+
/**
16+
* Get the repository name for a given version
17+
* Nextcloud moved to nextcloud-releases/server starting with version 32
18+
*/
19+
function get_repo_for_version(int $version): string {
20+
return $version >= 32 ? 'nextcloud-releases/server' : 'nextcloud/server';
21+
}
22+
23+
/**
24+
* Check if a version is officially released by querying the GitHub tag
25+
*/
26+
function is_version_released(int $version): bool {
27+
$repo = get_repo_for_version($version);
28+
$url = sprintf('https://api.github.com/repos/%s/releases/tags/v%d.0.0', $repo, $version);
29+
30+
$context = stream_context_create([
31+
'http' => [
32+
'header' => get_github_headers(),
33+
'timeout' => 5,
34+
'ignore_errors' => true
35+
]
36+
]);
37+
38+
$response = @file_get_contents($url, false, $context);
39+
40+
if (isset($http_response_header) && is_array($http_response_header)) {
41+
return strpos($http_response_header[0] ?? '', '200') !== false;
42+
}
43+
44+
return $response !== false;
45+
}
46+
47+
/**
48+
* Fetch release date for a version from GitHub API
49+
*/
50+
function get_release_date(int $version): ?int {
51+
$repo = get_repo_for_version($version);
52+
$url = sprintf('https://api.github.com/repos/%s/releases/tags/v%d.0.0', $repo, $version);
53+
54+
$context = stream_context_create([
55+
'http' => [
56+
'header' => get_github_headers(),
57+
'timeout' => 5
58+
]
59+
]);
60+
61+
$response = @file_get_contents($url, false, $context);
62+
if ($response === false) {
63+
return null;
64+
}
65+
66+
$data = json_decode($response, true);
67+
$publishedAt = $data['published_at'] ?? $data['created_at'] ?? null;
68+
return $publishedAt ? strtotime($publishedAt) : null;
69+
}
70+
71+
// Parse and validate command-line arguments
472
/** @var int[] */
573
$branches = array_slice($argv, 1);
674
$branches = array_filter($branches, fn($b) => is_numeric($b));
775
rsort($branches, SORT_NUMERIC);
8-
9-
// We added this script when we had only 12 and above.
1076
$branches = array_filter($branches, fn($b) => $b >= 12);
1177

1278
if (empty($branches)) {
1379
fwrite(STDERR, "Error: No valid stable branches were provided. Please pass at least one numeric branch >= 12.\n");
1480
exit(1);
1581
}
1682

17-
// Generate current development version section
18-
$sections = [generate_section(($branches[0] + 1), 0)];
83+
// Fetch release information from GitHub
84+
$released_branches = [];
85+
$oneYearAgo = time() - (365 * 24 * 60 * 60);
86+
$foundOutOfSupport = false;
1987

20-
// Generate stable version section
21-
foreach ($branches as $index => $v) {
22-
$sections[] = generate_section($v, $index + 1);
88+
foreach ($branches as $branch) {
89+
if ($foundOutOfSupport) {
90+
fwrite(STDERR, "⊘ Version $branch not checked (older than first out-of-support version)\n");
91+
continue;
92+
}
93+
94+
if (!is_version_released($branch)) {
95+
fwrite(STDERR, "✗ Version $branch not released (tag v$branch.0.0 not found)\n");
96+
continue;
97+
}
98+
99+
$releaseTime = get_release_date($branch);
100+
if ($releaseTime === null) {
101+
$releaseTime = time();
102+
}
103+
104+
$released_branches[$branch] = $releaseTime;
105+
$releaseType = $releaseTime >= $oneYearAgo ? 'stable' : 'unsupported';
106+
fwrite(STDERR, "✓ Version $branch released on " . date('Y-m-d', $releaseTime) . " ($releaseType)\n");
107+
108+
if ($releaseType === 'unsupported') {
109+
$foundOutOfSupport = true;
110+
}
23111
}
24112

25-
// Insert into index.html
26-
$index = file_get_contents(__DIR__ . '/index-template.html');
113+
// Determine development version
114+
if (isset($released_branches[$branches[0]])) {
115+
$devVersion = $branches[0] + 1;
116+
$devStatus = 'development';
117+
} else {
118+
$devVersion = $branches[0];
119+
$devStatus = 'upcoming';
120+
}
27121

28-
$supported = array_slice($sections, 0, 4);
29-
$legacy = array_slice($sections, 4);
122+
fwrite(STDERR, "⬗ Version $devVersion ($devStatus)\n");
30123

31-
$index = str_replace(
32-
'<!-- SERVER SUPPORTED BLOCK -->',
33-
implode("\n", $supported),
34-
$index
35-
);
124+
// Collect released stable versions within support window
125+
$stableVersions = [];
126+
foreach ($branches as $branch) {
127+
if (isset($released_branches[$branch]) && $released_branches[$branch] >= $oneYearAgo) {
128+
$stableVersions[] = $branch;
129+
} elseif (isset($released_branches[$branch])) {
130+
// Once we hit an unsupported version, stop
131+
break;
132+
}
133+
}
36134

37-
$index = str_replace(
38-
'<!-- SERVER LEGACY BLOCK -->',
39-
implode("\n", $legacy),
40-
$index
41-
);
135+
// Generate sections with proper indices
136+
$supported = [generate_section($devVersion, 0)];
137+
foreach ($stableVersions as $idx => $version) {
138+
// Index 3 for the oldest supported version if there are multiple
139+
$index = ($idx + 1 === count($stableVersions) && count($stableVersions) > 1) ? 3 : $idx + 1;
140+
$supported[] = generate_section($version, $index);
141+
}
42142

43-
$index = str_replace(
44-
'<!-- CURRENT_YEAR -->',
45-
date('Y'),
46-
$index
47-
);
143+
// Generate legacy sections (released but outside support window)
144+
$legacy = [];
145+
foreach ($branches as $branch) {
146+
if (isset($released_branches[$branch]) && !in_array($branch, $stableVersions)) {
147+
$legacy[] = generate_section($branch, 1);
148+
}
149+
}
48150

151+
// Generate and write final index.html
152+
$index = file_get_contents(__DIR__ . '/index-template.html');
153+
$index = str_replace('<!-- SERVER SUPPORTED BLOCK -->', implode("\n", $supported), $index);
154+
$index = str_replace('<!-- SERVER LEGACY BLOCK -->', implode("\n", $legacy), $index);
155+
$index = str_replace('<!-- CURRENT_YEAR -->', date('Y'), $index);
49156
file_put_contents(__DIR__ . '/index.html', $index);

build/verify-index.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
/**
3+
* Verify index.html structure and links validity
4+
*
5+
* Checks:
6+
* - Template placeholders are replaced
7+
* - Version sections exist
8+
* - Links are properly formatted
9+
* - External links are accessible
10+
*/
11+
12+
$html_file = 'build/index.html';
13+
14+
if (!file_exists($html_file)) {
15+
fwrite(STDERR, "⚠️ $html_file not found\n");
16+
exit(0);
17+
}
18+
19+
$content = file_get_contents($html_file);
20+
21+
// Verify template placeholders are replaced
22+
if (strpos($content, 'SERVER SUPPORTED BLOCK') !== false || strpos($content, 'SERVER LEGACY BLOCK') !== false) {
23+
fwrite(STDERR, "❌ ERROR: Template placeholders not replaced in index.html!\n");
24+
exit(1);
25+
}
26+
27+
// Extract all href attributes
28+
$links = [];
29+
if (preg_match_all('/href=(["\'])([^\'"]+)\1/', $content, $matches)) {
30+
$links = array_unique($matches[2]);
31+
}
32+
33+
fprintf(STDERR, "✓ Found %d unique links in index.html\n", count($links));
34+
35+
// Categorize links
36+
$fragment_links = array_filter($links, fn($l) => strpos($l, '#') === 0);
37+
$external_links = array_filter($links, fn($l) => preg_match('~^https?://~', $l));
38+
$relative_links = array_filter($links, fn($l) => strpos($l, '#') !== 0 && !preg_match('~^https?://~', $l));
39+
40+
fprintf(STDERR, " - Fragment links: %d\n", count($fragment_links));
41+
fprintf(STDERR, " - External links: %d\n", count($external_links));
42+
fprintf(STDERR, " - Relative links: %d\n", count($relative_links));
43+
44+
// Verify structure is present
45+
if (!preg_match('/<h2>Nextcloud \d+/', $content)) {
46+
fwrite(STDERR, "❌ ERROR: No version sections found in index.html!\n");
47+
exit(1);
48+
}
49+
50+
$version_count = preg_match_all('/<h2>Nextcloud (\d+)/', $content, $versions);
51+
fprintf(STDERR, "✓ Found %d version sections: %s\n", $version_count, implode(', ', $versions[1]));
52+
53+
// Validate documentation links format (should be /server/VERSION/)
54+
$docs_links = array_filter($external_links, fn($l) => preg_match('~docs\.[^/]+/server/~', $l));
55+
56+
if (!empty($docs_links)) {
57+
$invalid_docs = array_filter($docs_links, fn($l) =>
58+
!preg_match('~/server/(latest|stable|\d+)/~', $l)
59+
);
60+
61+
if (!empty($invalid_docs)) {
62+
fprintf(STDERR, "⚠️ WARNING: %d potentially malformed documentation links detected:\n", count($invalid_docs));
63+
foreach (array_slice($invalid_docs, 0, 3) as $link) {
64+
fprintf(STDERR, " - %s\n", $link);
65+
}
66+
} else {
67+
fprintf(STDERR, "✓ All %d documentation links are properly formatted\n", count($docs_links));
68+
}
69+
}
70+
71+
// Check accessibility of external links (sample first 5)
72+
fprintf(STDERR, "\nVerifying external links...\n");
73+
$unreachable = [];
74+
$external_sample = array_slice($external_links, 0, 5);
75+
76+
foreach ($external_sample as $link) {
77+
$context = stream_context_create([
78+
'http' => [
79+
'method' => 'HEAD',
80+
'timeout' => 5,
81+
'ignore_errors' => true,
82+
'user_agent' => 'Mozilla/5.0'
83+
]
84+
]);
85+
86+
$response = @get_headers($link, true, $context);
87+
88+
if ($response === false) {
89+
fprintf(STDERR, " ⚠️ %s - connection failed (may be temporary)\n", $link);
90+
} else {
91+
$status_line = $response[0] ?? '';
92+
if (preg_match('/\d{3}/', $status_line, $match)) {
93+
$status = (int)$match[0];
94+
if ($status >= 400) {
95+
$unreachable[] = [$link, $status];
96+
fprintf(STDERR, " ⚠️ %s returned %d\n", $link, $status);
97+
} else {
98+
fprintf(STDERR, " ✓ %s\n", $link);
99+
}
100+
}
101+
}
102+
}
103+
104+
if (count($unreachable) >= 3) {
105+
fprintf(STDERR, "\n❌ ERROR: %d links are unreachable!\n", count($unreachable));
106+
exit(1);
107+
}
108+
109+
fprintf(STDERR, "\n✓ index.html validation passed\n");
110+
exit(0);

0 commit comments

Comments
 (0)