Skip to content

Commit 138c393

Browse files
authored
Merge branch 'master' into fix/from-unixtime-doc-return-type-26637
2 parents 8e8eddd + c781182 commit 138c393

File tree

15 files changed

+1443
-277
lines changed

15 files changed

+1443
-277
lines changed

.github/actions/report-broken-links/action.yml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,18 @@ runs:
4444
4545
# Check if comment file was created and has content
4646
if [[ -f comment.md && -s comment.md ]]; then
47-
echo "has-broken-links=true" >> $GITHUB_OUTPUT
47+
echo "comment-generated=true" >> $GITHUB_OUTPUT
4848
4949
# Count broken links by parsing the comment
5050
broken_count=$(grep -o "Found [0-9]* broken link" comment.md | grep -o "[0-9]*" || echo "0")
5151
echo "broken-link-count=$broken_count" >> $GITHUB_OUTPUT
52-
echo "comment-generated=true" >> $GITHUB_OUTPUT
52+
53+
# Check if there are actually broken links (not just a success comment)
54+
if [[ "$broken_count" -gt 0 ]]; then
55+
echo "has-broken-links=true" >> $GITHUB_OUTPUT
56+
else
57+
echo "has-broken-links=false" >> $GITHUB_OUTPUT
58+
fi
5359
else
5460
echo "has-broken-links=false" >> $GITHUB_OUTPUT
5561
echo "broken-link-count=0" >> $GITHUB_OUTPUT
@@ -78,10 +84,20 @@ runs:
7884
}
7985
}
8086
81-
- name: Set workflow status
82-
if: steps.generate-comment.outputs.has-broken-links == 'true'
87+
- name: Report validation results
8388
run: |
89+
has_broken_links="${{ steps.generate-comment.outputs.has-broken-links }}"
8490
broken_count="${{ steps.generate-comment.outputs.broken-link-count }}"
85-
echo "::error::Found $broken_count broken link(s)"
86-
exit 1
91+
92+
if [ "$has_broken_links" = "true" ]; then
93+
echo "::error::❌ Link validation failed: Found $broken_count broken link(s)"
94+
echo "Check the PR comment for detailed broken link information"
95+
exit 1
96+
else
97+
echo "::notice::✅ Link validation passed successfully"
98+
echo "All links in the changed files are valid"
99+
if [ "${{ steps.generate-comment.outputs.comment-generated }}" = "true" ]; then
100+
echo "PR comment posted with validation summary and cache statistics"
101+
fi
102+
fi
87103
shell: bash

.github/actions/setup-docs-env/action.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,18 @@ runs:
1212

1313
- name: Install dependencies
1414
run: yarn install
15+
shell: bash
16+
17+
- name: Verify Hugo installation
18+
run: |
19+
echo "Checking Hugo availability..."
20+
if command -v hugo &> /dev/null; then
21+
echo "✅ Hugo found on PATH: $(which hugo)"
22+
hugo version
23+
else
24+
echo "⚠️ Hugo not found on PATH, will use project-local Hugo via yarn"
25+
fi
26+
27+
echo "Checking yarn hugo command..."
28+
yarn hugo version || echo "⚠️ Project Hugo not available via yarn"
1529
shell: bash

.github/actions/validate-links/action.yml

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ inputs:
1717
description: 'Cache key prefix for this validation run'
1818
required: false
1919
default: 'link-validation'
20+
timeout:
21+
description: 'Test timeout in seconds'
22+
required: false
23+
default: '900'
2024

2125
outputs:
2226
failed:
@@ -44,39 +48,59 @@ runs:
4448
export GITHUB_ACTIONS=true
4549
export NODE_OPTIONS="--max-old-space-size=4096"
4650
47-
# Add timeout to prevent hanging
48-
timeout ${{ inputs.timeout }} node cypress/support/run-e2e-specs.js ${{ inputs.files }} \
51+
# Set test runner timeout for Hugo shutdown
52+
export HUGO_SHUTDOWN_TIMEOUT=5000
53+
54+
# Add timeout to prevent hanging (timeout command syntax: timeout DURATION COMMAND)
55+
timeout ${{ inputs.timeout }}s node cypress/support/run-e2e-specs.js ${{ inputs.files }} \
4956
--spec cypress/e2e/content/article-links.cy.js || {
5057
exit_code=$?
51-
echo "::error::Link validation failed with exit code $exit_code"
5258
53-
# Check for specific error patterns
59+
# Handle timeout specifically
60+
if [ $exit_code -eq 124 ]; then
61+
echo "::error::Link validation timed out after ${{ inputs.timeout }} seconds"
62+
echo "::notice::This may indicate Hugo server startup issues or very slow link validation"
63+
else
64+
echo "::error::Link validation failed with exit code $exit_code"
65+
fi
66+
67+
# Check for specific error patterns and logs (but don't dump full content)
68+
if [ -f /tmp/hugo_server.log ]; then
69+
echo "Hugo server log available for debugging"
70+
fi
71+
5472
if [ -f hugo.log ]; then
55-
echo "::group::Hugo Server Logs"
56-
cat hugo.log
57-
echo "::endgroup::"
73+
echo "Additional Hugo log available for debugging"
5874
fi
5975
6076
if [ -f /tmp/broken_links_report.json ]; then
61-
echo "::group::Broken Links Report"
62-
cat /tmp/broken_links_report.json
63-
echo "::endgroup::"
77+
# Only show summary, not full report (full report is uploaded as artifact)
78+
broken_count=$(grep -o '"url":' /tmp/broken_links_report.json | wc -l || echo "0")
79+
echo "Broken links report contains $broken_count entries"
6480
fi
6581
6682
exit $exit_code
6783
}
6884
85+
# Report success if we get here
86+
echo "::notice::✅ Link validation completed successfully"
87+
echo "No broken links detected in the tested files"
88+
6989
- name: Upload logs on failure
7090
if: failure()
7191
uses: actions/upload-artifact@v4
7292
with:
73-
name: validation-logs-${{ inputs.product-name }}
93+
name: validation-logs-${{ inputs.product-name && inputs.product-name || 'default' }}
7494
path: |
7595
hugo.log
96+
/tmp/hugo_server.log
97+
if-no-files-found: ignore
98+
7699

77100
- name: Upload broken links report
78-
if: failure()
101+
if: always()
79102
uses: actions/upload-artifact@v4
80103
with:
81104
name: broken-links-report${{ inputs.product-name && format('-{0}', inputs.product-name) || '' }}
82-
path: /tmp/broken_links_report.json
105+
path: /tmp/broken_links_report.json
106+
if-no-files-found: ignore

.github/scripts/cache-manager.cjs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Simple Cache Manager for Link Validation Results
5+
* Uses GitHub Actions cache API or local file storage
6+
*/
7+
8+
const fs = require('fs');
9+
const path = require('path');
10+
const crypto = require('crypto');
11+
const process = require('process');
12+
13+
const CACHE_VERSION = 'v1';
14+
const CACHE_KEY_PREFIX = 'link-validation';
15+
const LOCAL_CACHE_DIR = path.join(process.cwd(), '.cache', 'link-validation');
16+
17+
/**
18+
* Simple cache interface
19+
*/
20+
class CacheManager {
21+
constructor(options = {}) {
22+
this.useGitHubCache =
23+
options.useGitHubCache !== false && process.env.GITHUB_ACTIONS;
24+
this.localCacheDir = options.localCacheDir || LOCAL_CACHE_DIR;
25+
26+
// Configurable cache TTL - default 30 days, support environment variable
27+
this.cacheTTLDays =
28+
options.cacheTTLDays || parseInt(process.env.LINK_CACHE_TTL_DAYS) || 30;
29+
this.maxAge = this.cacheTTLDays * 24 * 60 * 60 * 1000;
30+
31+
if (!this.useGitHubCache) {
32+
this.ensureLocalCacheDir();
33+
}
34+
}
35+
36+
ensureLocalCacheDir() {
37+
if (!fs.existsSync(this.localCacheDir)) {
38+
fs.mkdirSync(this.localCacheDir, { recursive: true });
39+
}
40+
}
41+
42+
generateCacheKey(filePath, fileHash) {
43+
const pathHash = crypto
44+
.createHash('sha256')
45+
.update(filePath)
46+
.digest('hex')
47+
.substring(0, 8);
48+
return `${CACHE_KEY_PREFIX}-${CACHE_VERSION}-${pathHash}-${fileHash}`;
49+
}
50+
51+
async get(filePath, fileHash) {
52+
if (this.useGitHubCache) {
53+
return await this.getFromGitHubCache(filePath, fileHash);
54+
} else {
55+
return await this.getFromLocalCache(filePath, fileHash);
56+
}
57+
}
58+
59+
async set(filePath, fileHash, results) {
60+
if (this.useGitHubCache) {
61+
return await this.setToGitHubCache(filePath, fileHash, results);
62+
} else {
63+
return await this.setToLocalCache(filePath, fileHash, results);
64+
}
65+
}
66+
67+
async getFromGitHubCache(filePath, fileHash) {
68+
// TODO: This method is a placeholder for GitHub Actions cache integration
69+
// GitHub Actions cache is handled directly in the workflow via actions/cache
70+
// This method should either be implemented or removed in future versions
71+
console.warn(
72+
'[PLACEHOLDER] getFromGitHubCache: Using placeholder implementation - always returns null'
73+
);
74+
return null;
75+
}
76+
77+
async setToGitHubCache(filePath, fileHash, results) {
78+
// TODO: This method is a placeholder for GitHub Actions cache integration
79+
// GitHub Actions cache is handled directly in the workflow via actions/cache
80+
// This method should either be implemented or removed in future versions
81+
console.warn(
82+
'[PLACEHOLDER] setToGitHubCache: Using placeholder implementation - always returns true'
83+
);
84+
return true;
85+
}
86+
87+
async getFromLocalCache(filePath, fileHash) {
88+
const cacheKey = this.generateCacheKey(filePath, fileHash);
89+
const cacheFile = path.join(this.localCacheDir, `${cacheKey}.json`);
90+
91+
if (!fs.existsSync(cacheFile)) {
92+
return null;
93+
}
94+
95+
try {
96+
const content = fs.readFileSync(cacheFile, 'utf8');
97+
const cached = JSON.parse(content);
98+
99+
// TTL check using configured cache duration
100+
const age = Date.now() - new Date(cached.cachedAt).getTime();
101+
102+
if (age > this.maxAge) {
103+
fs.unlinkSync(cacheFile);
104+
return null;
105+
}
106+
107+
return cached.results;
108+
} catch (error) {
109+
// Clean up corrupted cache
110+
try {
111+
fs.unlinkSync(cacheFile);
112+
} catch {
113+
// Ignore cleanup errors
114+
}
115+
return null;
116+
}
117+
}
118+
119+
async setToLocalCache(filePath, fileHash, results) {
120+
const cacheKey = this.generateCacheKey(filePath, fileHash);
121+
const cacheFile = path.join(this.localCacheDir, `${cacheKey}.json`);
122+
123+
const cacheData = {
124+
filePath,
125+
fileHash,
126+
results,
127+
cachedAt: new Date().toISOString(),
128+
};
129+
130+
try {
131+
fs.writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2));
132+
return true;
133+
} catch (error) {
134+
console.warn(`Cache save failed: ${error.message}`);
135+
return false;
136+
}
137+
}
138+
139+
async cleanup() {
140+
if (this.useGitHubCache) {
141+
return { removed: 0, note: 'GitHub Actions cache auto-managed' };
142+
}
143+
144+
let removed = 0;
145+
if (!fs.existsSync(this.localCacheDir)) {
146+
return { removed };
147+
}
148+
149+
const files = fs.readdirSync(this.localCacheDir);
150+
151+
for (const file of files) {
152+
if (!file.endsWith('.json')) continue;
153+
154+
const filePath = path.join(this.localCacheDir, file);
155+
try {
156+
const stat = fs.statSync(filePath);
157+
if (Date.now() - stat.mtime.getTime() > this.maxAge) {
158+
fs.unlinkSync(filePath);
159+
removed++;
160+
}
161+
} catch {
162+
// Remove corrupted files
163+
try {
164+
fs.unlinkSync(filePath);
165+
removed++;
166+
} catch {
167+
// Ignore errors
168+
}
169+
}
170+
}
171+
172+
return { removed };
173+
}
174+
}
175+
176+
module.exports = CacheManager;
177+
module.exports.CacheManager = CacheManager;

0 commit comments

Comments
 (0)