Skip to content

Commit 433f292

Browse files
authored
Merge branch 'master' into master
2 parents 41a44ca + a0e4db9 commit 433f292

File tree

139 files changed

+6512
-866
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+6512
-866
lines changed

.github/pull_request_template.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,10 @@
22
- [ ] You wrote all of your code in 1 single HTML file.
33
- [ ] Your file is less than 1mb.
44
- [ ] You didn't import any external files (e.g. images, stylesheets or js files).
5-
- [ ] There are no incoming or outgoing network requests.
5+
- [ ] There are no incoming or outgoing network requests.
6+
- [ ] Your HTML file includes the required meta tags in the `<head>` section:
7+
- `<meta name="description" content="Brief description of your entry">`
8+
- `<meta name="author" content="Your Name">`
9+
- `<meta name="github" content="your-github-username">`
10+
11+
**Note:** You do NOT need to edit `entries.js` - it's automatically generated from your HTML meta tags!

.github/scripts/comment-on-pr.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
const fs = require('fs');
2+
const { Octokit } = require('@octokit/rest');
3+
4+
// Get environment variables
5+
const token = process.env.GITHUB_TOKEN;
6+
const repo = process.env.GITHUB_REPOSITORY.split('/');
7+
const owner = repo[0];
8+
const repoName = repo[1];
9+
const prNumber = process.env.PR_NUMBER;
10+
const isDryRun = process.env.DRY_RUN === 'true';
11+
12+
if (!token) {
13+
console.error('GITHUB_TOKEN environment variable is required');
14+
process.exit(1);
15+
}
16+
17+
if (!prNumber) {
18+
console.error('PR_NUMBER environment variable is required');
19+
process.exit(1);
20+
}
21+
22+
const octokit = new Octokit({ auth: token });
23+
24+
async function commentOnPR() {
25+
try {
26+
let results = {};
27+
let hasErrors = false;
28+
29+
if (fs.existsSync('validation-results.json')) {
30+
results = JSON.parse(fs.readFileSync('validation-results.json', 'utf8'));
31+
hasErrors = Object.values(results).some(issues => issues.length > 0);
32+
}
33+
34+
// Determine overall status
35+
const status = hasErrors ? '🚫 Entry Validation Failed' : '✅ Entry Validation Passed';
36+
let comment = `## ${status}\n\n`;
37+
38+
// Add requirements checklist
39+
comment += '### Requirements Checklist:\n\n';
40+
41+
// Check each requirement across all files
42+
let fileSizePass = true;
43+
let htmlStructurePass = true;
44+
let noExternalImportsPass = true;
45+
let noNetworkRequestsPass = true;
46+
let htmlSyntaxPass = true;
47+
let entryRegisteredPass = true;
48+
49+
for (const [file, issues] of Object.entries(results)) {
50+
issues.forEach(issue => {
51+
if (issue.includes('File size') && issue.includes('exceeds')) fileSizePass = false;
52+
if (issue.includes('File must have') || issue.includes('HTML structure')) htmlStructurePass = false;
53+
if (issue.includes('External') && (issue.includes('stylesheet') || issue.includes('script') || issue.includes('image') || issue.includes('resource'))) noExternalImportsPass = false;
54+
if (issue.includes('network request')) noNetworkRequestsPass = false;
55+
if (issue.includes('HTML syntax') || issue.includes('Unclosed')) htmlSyntaxPass = false;
56+
if (issue.includes('Entry not found') || issue.includes('entries.js')) entryRegisteredPass = false;
57+
});
58+
}
59+
60+
comment += `- ${fileSizePass ? '✅' : '❌'} File must be less than 1MB\n`;
61+
comment += `- ${htmlStructurePass ? '✅' : '❌'} Valid HTML file structure\n`;
62+
comment += `- ${noExternalImportsPass ? '✅' : '❌'} No external file imports (images, CSS, JS)\n`;
63+
comment += `- ${noNetworkRequestsPass ? '✅' : '❌'} No network requests\n`;
64+
comment += `- ${htmlSyntaxPass ? '✅' : '❌'} Valid HTML syntax\n`;
65+
comment += `- ${entryRegisteredPass ? '✅' : '❌'} Entry registered in entries.js\n\n`;
66+
67+
// Add detailed issues if any exist
68+
if (hasErrors) {
69+
comment += '### Issues Found:\n\n';
70+
for (const [file, issues] of Object.entries(results)) {
71+
if (issues.length > 0) {
72+
comment += `**${file}:**\n`;
73+
issues.forEach(issue => {
74+
comment += `- ${issue}\n`;
75+
});
76+
comment += '\n';
77+
}
78+
}
79+
comment += 'Please fix the issues above and update your pull request.';
80+
} else {
81+
comment += 'All entries meet the One HTML Page Challenge requirements! 🎉';
82+
}
83+
84+
// Log the comment for dry runs or post it
85+
if (isDryRun) {
86+
console.log('Dry run mode - comment would be:');
87+
console.log('---');
88+
console.log(comment);
89+
console.log('---');
90+
} else {
91+
console.log('Posting comment to PR #' + prNumber);
92+
await octokit.rest.issues.createComment({
93+
owner: owner,
94+
repo: repoName,
95+
issue_number: parseInt(prNumber),
96+
body: comment
97+
});
98+
console.log('Comment posted successfully');
99+
}
100+
101+
// Exit with error code if validation failed
102+
if (hasErrors) {
103+
process.exit(1);
104+
} else {
105+
process.exit(0);
106+
}
107+
108+
} catch (error) {
109+
console.error('Error posting comment:', error.message);
110+
process.exit(1);
111+
}
112+
}
113+
114+
commentOnPR();
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const cheerio = require('cheerio');
4+
5+
/**
6+
* Extract metadata from an HTML file
7+
* @param {string} filePath - Path to the HTML file
8+
* @returns {object|null} - Extracted metadata or null if invalid
9+
*/
10+
function extractMetadata(filePath) {
11+
try {
12+
const content = fs.readFileSync(filePath, 'utf8');
13+
const $ = cheerio.load(content);
14+
const filename = path.basename(filePath);
15+
16+
// Extract metadata from meta tags and title
17+
const title = $('meta[name="title"]').attr('content') || $('title').text() || filename.replace(/\.[^/.]+$/, "");
18+
const description = $('meta[name="description"]').attr('content') || '';
19+
const author = $('meta[name="author"]').attr('content') || '';
20+
const github = $('meta[name="github"]').attr('content') || '';
21+
22+
// Extract compatible browsers if specified
23+
const compatibleBrowsersContent = $('meta[name="compatible-browsers"]').attr('content');
24+
let compatibleBrowsers = [];
25+
if (compatibleBrowsersContent) {
26+
compatibleBrowsers = compatibleBrowsersContent.split(',').map(b => b.trim()).filter(b => b);
27+
}
28+
29+
// Build entry object
30+
const entry = {
31+
title: title.trim(),
32+
filename: filename
33+
};
34+
35+
if (description) entry.description = description.trim();
36+
if (author) entry.author = author.trim();
37+
if (github) entry.github = github.trim();
38+
if (compatibleBrowsers.length > 0) entry.compatibleBrowsers = compatibleBrowsers;
39+
40+
return entry;
41+
} catch (error) {
42+
console.error(`Error processing ${filePath}:`, error.message);
43+
return null;
44+
}
45+
}
46+
47+
/**
48+
* Generate entries.js from all HTML files in the entries directory
49+
*/
50+
function generateEntriesJs() {
51+
const entriesDir = 'entries';
52+
53+
if (!fs.existsSync(entriesDir)) {
54+
console.error('Entries directory not found');
55+
process.exit(1);
56+
}
57+
58+
const entries = [];
59+
const files = fs.readdirSync(entriesDir)
60+
.filter(file => file.match(/\.html?$/i))
61+
.sort(); // Sort alphabetically for consistent output
62+
63+
console.log(`Processing ${files.length} HTML files...`);
64+
65+
for (const file of files) {
66+
const filePath = path.join(entriesDir, file);
67+
const metadata = extractMetadata(filePath);
68+
69+
if (metadata) {
70+
entries.push(metadata);
71+
console.log(`✅ ${file}: ${metadata.title}`);
72+
} else {
73+
console.log(`❌ ${file}: Failed to extract metadata`);
74+
}
75+
}
76+
77+
// Sort entries by title for consistency with current entries.js
78+
entries.sort((a, b) => a.title.localeCompare(b.title));
79+
80+
// Write JavaScript file
81+
const jsContent = `/**
82+
* This file is automatically generated from HTML meta tags.
83+
* Last updated: ${new Date().toISOString()}
84+
*
85+
* DO NOT EDIT MANUALLY - Changes will be overwritten!
86+
* To update entries, modify the meta tags in your HTML files.
87+
*/
88+
89+
const entries = ${JSON.stringify(entries, null, 2)};`;
90+
91+
const jsOutputFile = 'entries.js';
92+
fs.writeFileSync(jsOutputFile, jsContent, 'utf8');
93+
94+
console.log(`\n✅ Generated ${jsOutputFile} with ${entries.length} entries`);
95+
96+
// Log any files missing metadata
97+
const missingMetadata = files.filter(file => {
98+
const filePath = path.join(entriesDir, file);
99+
const metadata = extractMetadata(filePath);
100+
return !metadata || !metadata.title || !metadata.description || !metadata.author;
101+
});
102+
103+
if (missingMetadata.length > 0) {
104+
console.log(`\n⚠️ Files missing complete metadata:`);
105+
missingMetadata.forEach(file => {
106+
const filePath = path.join(entriesDir, file);
107+
const metadata = extractMetadata(filePath);
108+
const missing = [];
109+
if (!metadata || !metadata.title) missing.push('title');
110+
if (!metadata || !metadata.description) missing.push('description');
111+
if (!metadata || !metadata.author) missing.push('author');
112+
console.log(` ${file}: missing ${missing.join(', ')}`);
113+
});
114+
}
115+
}
116+
117+
// Run the generator
118+
generateEntriesJs();

0 commit comments

Comments
 (0)