From c37996a4107e4ec3cabba6d3c94220303c7331c0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 20 Aug 2025 03:23:56 +0000
Subject: [PATCH 1/6] Initial plan
From 14ee08aac345a40b1b383b1496faa7753acf02d0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 20 Aug 2025 03:34:45 +0000
Subject: [PATCH 2/6] Implement structured data HTML validate plugin
Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com>
---
.htmlvalidate.mjs | 1 +
test/fixtures/required-results.json | 34 +++--
test/plugin.html-validate.mjs | 3 +
test/plugin.html-validate.structured-data.mjs | 134 ++++++++++++++++++
4 files changed, 161 insertions(+), 11 deletions(-)
create mode 100644 test/plugin.html-validate.structured-data.mjs
diff --git a/.htmlvalidate.mjs b/.htmlvalidate.mjs
index 56a5106..d696767 100644
--- a/.htmlvalidate.mjs
+++ b/.htmlvalidate.mjs
@@ -11,6 +11,7 @@ export default defineConfig({
"pacific-medical-training/latest-packages": "error",
"pacific-medical-training/https-links": "error",
"pacific-medical-training/internal-links": "error",
+ "pacific-medical-training/structured-data": "error",
"wcag/h37": [
"error",
{
diff --git a/test/fixtures/required-results.json b/test/fixtures/required-results.json
index a818324..e72b04c 100644
--- a/test/fixtures/required-results.json
+++ b/test/fixtures/required-results.json
@@ -71,17 +71,6 @@
}
],
"test/fixtures/ensure-https.html": [
- {
- "ruleId": "pacific-medical-training/https-links",
- "severity": 2,
- "message": "external link is insecure and accessible via HTTPS: http://en.wikipedia.org/wiki/Horse",
- "offset": 196,
- "line": 9,
- "column": 6,
- "size": 1,
- "selector": "html > body > a",
- "ruleUrl": "https://github.com/fulldecent/github-pages-template/#https-links"
- },
{
"ruleId": "pacific-medical-training/external-links",
"severity": 2,
@@ -141,5 +130,28 @@
"selector": "html > body > img",
"ruleUrl": "https://html-validate.org/rules/wcag/h37.html"
}
+ ],
+ "test/fixtures/valid-jsonld.html": [
+ {
+ "ruleId": "pacific-medical-training/canonical-link",
+ "severity": 2,
+ "message": "
is missing ",
+ "size": 0,
+ "selector": null,
+ "ruleUrl": "https://github.com/fulldecent/github-pages-template/#canonical"
+ }
+ ],
+ "test/fixtures/invalid-jsonld.html": [
+ {
+ "ruleId": "pacific-medical-training/structured-data",
+ "severity": 2,
+ "message": "JSON-LD parse error in structured data",
+ "offset": 279,
+ "line": 11,
+ "column": 39,
+ "size": 1,
+ "selector": "html > body > script",
+ "ruleUrl": "https://github.com/fulldecent/github-pages-template/#structured-data"
+ }
]
}
diff --git a/test/plugin.html-validate.mjs b/test/plugin.html-validate.mjs
index 57e4ed9..cdbd0ed 100644
--- a/test/plugin.html-validate.mjs
+++ b/test/plugin.html-validate.mjs
@@ -7,6 +7,7 @@ import CanonicalLinkRule from "./plugin.html-validate.canonical-link.mjs";
import LatestPackagesRules from "./plugin.html-validate.latest-packages.mjs";
import EnsureHttpsRules from "./plugin.html-validate.https-links.mjs";
import CheckInternalLinks from "./plugin.html-validate.internal-links.mjs";
+import StructuredDataRule from "./plugin.html-validate.structured-data.mjs";
export default definePlugin({
name: "pacific-medical-training",
@@ -18,6 +19,7 @@ export default definePlugin({
"pacific-medical-training/latest-packages": LatestPackagesRules,
"pacific-medical-training/https-links": EnsureHttpsRules,
"pacific-medical-training/internal-links": CheckInternalLinks,
+ "pacific-medical-training/structured-data": StructuredDataRule,
},
configs: {
recommended: {
@@ -29,6 +31,7 @@ export default definePlugin({
"pacific-medical-training/latest-packages": "error",
"pacific-medical-training/https-links": "error",
"pacific-medical-training/internal-links": "error",
+ "pacific-medical-training/structured-data": "error",
},
},
},
diff --git a/test/plugin.html-validate.structured-data.mjs b/test/plugin.html-validate.structured-data.mjs
new file mode 100644
index 0000000..1e798b6
--- /dev/null
+++ b/test/plugin.html-validate.structured-data.mjs
@@ -0,0 +1,134 @@
+import { Rule } from "html-validate";
+import { execSync } from "child_process";
+import fs from "fs";
+import path from "path";
+
+export default class StructuredDataRule extends Rule {
+ documentation() {
+ return {
+ description: "Validate JSON-LD structured data using structured-data-testing-tool",
+ url: "https://github.com/fulldecent/github-pages-template/#structured-data",
+ };
+ }
+
+ setup() {
+ this.on("tag:ready", this.tagReady.bind(this));
+ }
+
+ tagReady({ target }) {
+ if (target.tagName === "script") {
+ const type = target.getAttribute("type")?.value;
+
+ // Only process script tags with type="application/ld+json"
+ if (type === "application/ld+json") {
+ this.validateJsonLd(target);
+ }
+ }
+ }
+
+ validateJsonLd(scriptElement) {
+ // Try to read the file content directly and extract the script
+ if (scriptElement.location && scriptElement.location.filename) {
+ try {
+ const fileContent = fs.readFileSync(scriptElement.location.filename, 'utf8');
+ const lines = fileContent.split('\n');
+
+ // Find script tag boundaries
+ let startLine = -1;
+ let endLine = -1;
+ let inScript = false;
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ if (line.includes('')) {
+ endLine = i;
+ break;
+ }
+ }
+
+ if (startLine >= 0 && endLine >= 0) {
+ const scriptContent = lines.slice(startLine, endLine).join('\n').trim();
+
+ if (scriptContent) {
+ this.testStructuredData(scriptContent, scriptElement);
+ return;
+ }
+ }
+ } catch (error) {
+ this.report({
+ node: scriptElement,
+ message: `Error reading file for structured data validation: ${error.message}`,
+ });
+ return;
+ }
+ }
+
+ this.report({
+ node: scriptElement,
+ message: "JSON-LD script tag is empty or cannot be read",
+ });
+ }
+
+ testStructuredData(content, scriptElement) {
+
+ // Create a temporary HTML file with just this JSON-LD script
+ const tempDir = "/tmp";
+ const tempFileName = `structured-data-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.html`;
+ const tempFilePath = path.join(tempDir, tempFileName);
+
+ try {
+ // Create minimal HTML with just the JSON-LD script
+ const tempHtml = `
+
+
+ Structured Data Test
+
+
+
+
+`;
+
+ fs.writeFileSync(tempFilePath, tempHtml);
+
+ // Run structured-data-testing-tool on the temporary file
+ const result = execSync(`yarn dlx structured-data-testing-tool --file "${tempFilePath}"`, {
+ encoding: "utf8",
+ stdio: "pipe",
+ });
+
+ // Check for errors in the output
+ if (result.includes("Error in jsonld parse")) {
+ this.report({
+ node: scriptElement,
+ message: "JSON-LD parse error in structured data",
+ });
+ } else if (result.includes("Failed:") && !result.includes("Failed: 0")) {
+ this.report({
+ node: scriptElement,
+ message: "Structured data validation failed",
+ });
+ }
+ // Note: We don't report warnings as errors, only actual failures
+
+ } catch (error) {
+ this.report({
+ node: scriptElement,
+ message: `Structured data testing error: ${error.message}`,
+ });
+ } finally {
+ // Clean up temporary file
+ try {
+ if (fs.existsSync(tempFilePath)) {
+ fs.unlinkSync(tempFilePath);
+ }
+ } catch (cleanupError) {
+ // Ignore cleanup errors
+ }
+ }
+ }
+}
\ No newline at end of file
From 2ae8c43258e4d83b8f68516f58735fb141e3ffaa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 20 Aug 2025 03:39:03 +0000
Subject: [PATCH 3/6] Complete structured data plugin integration
Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com>
---
README.md | 14 ++++------
package.json | 3 +--
test/plugin.html-validate.structured-data.mjs | 26 +++++++++----------
3 files changed, 18 insertions(+), 25 deletions(-)
diff --git a/README.md b/README.md
index e03ed2b..2821ff4 100644
--- a/README.md
+++ b/README.md
@@ -70,15 +70,11 @@ Perform website testing (you must have already [built the site](#build-the-site)
yarn test
```
-#### Structured Data Testing
-
-Test structured data (JSON-LD) validation specifically:
-
-```sh
-yarn test-structured-data
-```
-
-This validates that any `application/ld+json` scripts in `build/**/*.html` files have correct schema.org formats and valid JSON syntax.
+This includes validation of:
+- HTML structure and accessibility
+- External and internal links
+- Structured data (JSON-LD) validation using schema.org standards
+- Content quality checks
## Notes for VS Code
diff --git a/package.json b/package.json
index a78362a..5806983 100644
--- a/package.json
+++ b/package.json
@@ -14,8 +14,7 @@
"structured-data-testing-tool": "^4.5.0"
},
"scripts": {
- "test": "yarn node test/fixtures-html-validate-should-fail.mjs && yarn node test/fixtures-structured-data-should-fail.mjs && yarn node test/build-html-validate.mjs && yarn node test/dirty-words-checker.mjs && yarn node test/dirty-file-paths-checker.mjs && yarn node test/build-structured-data-validate.mjs",
- "test-structured-data": "yarn node test/build-structured-data-validate.mjs",
+ "test": "yarn node test/fixtures-html-validate-should-fail.mjs && yarn node test/build-html-validate.mjs && yarn node test/dirty-words-checker.mjs && yarn node test/dirty-file-paths-checker.mjs",
"lint": "yarn prettier-check && yarn markdownlint-check",
"lint-fix": "yarn prettier-fix && yarn markdownlint-fix",
"generate-sitemap": "node scripts/generate-sitemap.mjs",
diff --git a/test/plugin.html-validate.structured-data.mjs b/test/plugin.html-validate.structured-data.mjs
index 1e798b6..b532c32 100644
--- a/test/plugin.html-validate.structured-data.mjs
+++ b/test/plugin.html-validate.structured-data.mjs
@@ -18,7 +18,7 @@ export default class StructuredDataRule extends Rule {
tagReady({ target }) {
if (target.tagName === "script") {
const type = target.getAttribute("type")?.value;
-
+
// Only process script tags with type="application/ld+json"
if (type === "application/ld+json") {
this.validateJsonLd(target);
@@ -30,28 +30,28 @@ export default class StructuredDataRule extends Rule {
// Try to read the file content directly and extract the script
if (scriptElement.location && scriptElement.location.filename) {
try {
- const fileContent = fs.readFileSync(scriptElement.location.filename, 'utf8');
- const lines = fileContent.split('\n');
-
+ const fileContent = fs.readFileSync(scriptElement.location.filename, "utf8");
+ const lines = fileContent.split("\n");
+
// Find script tag boundaries
let startLine = -1;
let endLine = -1;
let inScript = false;
-
+
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('')) {
+ } else if (inScript && line.includes("")) {
endLine = i;
break;
}
}
-
+
if (startLine >= 0 && endLine >= 0) {
- const scriptContent = lines.slice(startLine, endLine).join('\n').trim();
-
+ const scriptContent = lines.slice(startLine, endLine).join("\n").trim();
+
if (scriptContent) {
this.testStructuredData(scriptContent, scriptElement);
return;
@@ -65,7 +65,7 @@ export default class StructuredDataRule extends Rule {
return;
}
}
-
+
this.report({
node: scriptElement,
message: "JSON-LD script tag is empty or cannot be read",
@@ -73,12 +73,11 @@ export default class StructuredDataRule extends Rule {
}
testStructuredData(content, scriptElement) {
-
// Create a temporary HTML file with just this JSON-LD script
const tempDir = "/tmp";
const tempFileName = `structured-data-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.html`;
const tempFilePath = path.join(tempDir, tempFileName);
-
+
try {
// Create minimal HTML with just the JSON-LD script
const tempHtml = `
@@ -114,7 +113,6 @@ ${content}
});
}
// Note: We don't report warnings as errors, only actual failures
-
} catch (error) {
this.report({
node: scriptElement,
@@ -131,4 +129,4 @@ ${content}
}
}
}
-}
\ No newline at end of file
+}
From bb8ee88d157e5f741766c209b88c248c87806381 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 15 Sep 2025 20:59:08 +0000
Subject: [PATCH 4/6] Fix build errors: format required-results.json and add
missing https-links rule
Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com>
---
test/fixtures/required-results.json | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/test/fixtures/required-results.json b/test/fixtures/required-results.json
index b75599f..231ce20 100644
--- a/test/fixtures/required-results.json
+++ b/test/fixtures/required-results.json
@@ -71,6 +71,17 @@
}
],
"test/fixtures/ensure-https.html": [
+ {
+ "ruleId": "pacific-medical-training/https-links",
+ "severity": 2,
+ "message": "external link is insecure and accessible via HTTPS: http://en.wikipedia.org/wiki/Horse",
+ "offset": 196,
+ "line": 9,
+ "column": 6,
+ "size": 1,
+ "selector": "html > body > a",
+ "ruleUrl": "https://github.com/fulldecent/github-pages-template/#https-links"
+ },
{
"ruleId": "pacific-medical-training/external-links",
"severity": 2,
@@ -216,4 +227,4 @@
"selector": "html > body > ul > li:nth-child(9) > a"
}
]
-}
\ No newline at end of file
+}
From 45c0d8d63d7e5ce2e3cb21889ffb700f1f06ddd5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 16 Sep 2025 20:33:51 +0000
Subject: [PATCH 5/6] Pull in latest changes from main branch and clean up
obsolete structured data test files
Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com>
---
test/build-structured-data-validate.mjs | 65 -------------------
test/fixtures-structured-data-should-fail.mjs | 58 -----------------
2 files changed, 123 deletions(-)
delete mode 100644 test/build-structured-data-validate.mjs
delete mode 100644 test/fixtures-structured-data-should-fail.mjs
diff --git a/test/build-structured-data-validate.mjs b/test/build-structured-data-validate.mjs
deleted file mode 100644
index 87b0b17..0000000
--- a/test/build-structured-data-validate.mjs
+++ /dev/null
@@ -1,65 +0,0 @@
-import { glob } from "glob";
-import { execSync } from "child_process";
-import fs from "fs";
-import path from "path";
-
-// Find and sort all HTML files in the 'build' directory
-const targets = glob.sync("build/**/*.html").sort();
-
-if (targets.length === 0) {
- console.log("โ ๏ธ No HTML files found in build directory");
- console.log(" Make sure to build the site first");
- process.exit(0);
-}
-
-console.log(`๐งช Validating structured data in ${targets.length} files...`);
-
-let hasErrors = false;
-
-// Validate each target file
-for (const target of targets) {
- try {
- // Use structured-data-testing-tool CLI to test the file
- const result = execSync(`yarn dlx structured-data-testing-tool --file "${target}"`, {
- encoding: "utf8",
- stdio: "pipe",
- });
-
- // Check if there are JSON-LD parse errors or failed tests in the output
- if (result.includes("Error in jsonld parse") || (result.includes("Failed:") && !result.includes("Failed: 0"))) {
- console.log("โ " + target);
- console.log(result);
- hasErrors = true;
- } else {
- console.log("โ
" + target);
- // Show a summary of structured data found
- const lines = result.split("\n");
- const schemaLine = lines.find((line) => line.includes("Schema.org schemas:"));
- if (schemaLine && !schemaLine.includes("Schema.org schemas: 0")) {
- console.log(` ๐ ${schemaLine.trim()}`);
- }
- // Only show detailed output if there are warnings
- if (result.includes("Warnings:") && !result.includes("Warnings: 0")) {
- console.log("โ ๏ธ Warnings found:");
- console.log(result);
- }
- }
- } catch (error) {
- console.log("โ " + target);
- console.log("โ ๏ธ Error testing file:", error.message);
- if (error.stdout) {
- console.log(error.stdout);
- }
- if (error.stderr) {
- console.error(error.stderr);
- }
- hasErrors = true;
- }
-}
-
-if (hasErrors) {
- console.log("โ Some tests failed.");
- process.exit(1);
-} else {
- console.log("โจ All tests passed!\n");
-}
diff --git a/test/fixtures-structured-data-should-fail.mjs b/test/fixtures-structured-data-should-fail.mjs
deleted file mode 100644
index 88dd473..0000000
--- a/test/fixtures-structured-data-should-fail.mjs
+++ /dev/null
@@ -1,58 +0,0 @@
-import { execSync } from "child_process";
-import path from "path";
-
-console.log("๐งช Testing structured data fixtures");
-
-const fixtures = [
- {
- file: "test/fixtures/valid-jsonld.html",
- shouldPass: true,
- description: "valid JSON-LD",
- },
- {
- file: "test/fixtures/invalid-jsonld.html",
- shouldPass: false,
- description: "invalid JSON-LD (syntax error)",
- },
-];
-
-let allTestsPassed = true;
-
-for (const fixture of fixtures) {
- try {
- const result = execSync(`yarn dlx structured-data-testing-tool --file "${fixture.file}"`, {
- encoding: "utf8",
- stdio: "pipe",
- });
-
- const hasError =
- result.includes("Error in jsonld parse") || (result.includes("Failed:") && !result.includes("Failed: 0"));
-
- const actuallyPassed = !hasError;
-
- if (actuallyPassed === fixture.shouldPass) {
- console.log(`โ
${fixture.file}: ${fixture.description} - ${actuallyPassed ? "passed" : "failed"} as expected`);
- } else {
- console.error(
- `โ ${fixture.file}: ${fixture.description} - expected ${fixture.shouldPass ? "pass" : "fail"} but got ${actuallyPassed ? "pass" : "fail"}`,
- );
- console.error("Output:", result);
- allTestsPassed = false;
- }
- } catch (error) {
- // execSync throws on non-zero exit codes, but structured-data-testing-tool doesn't always exit with error codes
- console.error(`โ ${fixture.file}: ${fixture.description} - execution error`);
- console.error("Error:", error.message);
- if (error.stdout) {
- console.error("Stdout:", error.stdout);
- }
- allTestsPassed = false;
- }
-}
-
-if (allTestsPassed) {
- console.log("โจ All fixtures produced expected results!\n");
-} else {
- console.error("โ Some fixture tests failed.");
- process.exit(1);
-}
From 6b5765a5f5e592b3bf281748c0edfafae8577329 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 16 Sep 2025 20:41:35 +0000
Subject: [PATCH 6/6] Improve structured data error messages with detailed
information from testing tool
Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com>
---
test/fixtures/required-results.json | 2 +-
test/plugin.html-validate.structured-data.mjs | 50 +++++++++++++++++--
2 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/test/fixtures/required-results.json b/test/fixtures/required-results.json
index 446e157..a166bc7 100644
--- a/test/fixtures/required-results.json
+++ b/test/fixtures/required-results.json
@@ -180,7 +180,7 @@
{
"ruleId": "pacific-medical-training/structured-data",
"severity": 2,
- "message": "JSON-LD parse error in structured data",
+ "message": "JSON-LD parse error: SyntaxError: Expected property name or '}' in JSON at position 11",
"offset": 279,
"line": 11,
"column": 39,
diff --git a/test/plugin.html-validate.structured-data.mjs b/test/plugin.html-validate.structured-data.mjs
index b532c32..46307c5 100644
--- a/test/plugin.html-validate.structured-data.mjs
+++ b/test/plugin.html-validate.structured-data.mjs
@@ -102,21 +102,65 @@ ${content}
// Check for errors in the output
if (result.includes("Error in jsonld parse")) {
+ // Extract the specific parse error message
+ const errorMatch = result.match(/Error in jsonld parse - (.+)/);
+ const errorDetail = errorMatch ? errorMatch[1] : "Unknown parse error";
this.report({
node: scriptElement,
- message: "JSON-LD parse error in structured data",
+ message: `JSON-LD parse error: ${errorDetail}`,
});
} else if (result.includes("Failed:") && !result.includes("Failed: 0")) {
+ // Extract failed test count and any test details
+ const failedMatch = result.match(/Failed: (\d+)/);
+ const failedCount = failedMatch ? failedMatch[1] : "some";
+
+ // Try to extract specific test failure information
+ const testFailures = [];
+ const lines = result.split("\n");
+ let inTestSection = false;
+
+ for (const line of lines) {
+ if (line.includes("Tests")) {
+ inTestSection = true;
+ continue;
+ }
+ if (line.includes("Statistics")) {
+ inTestSection = false;
+ break;
+ }
+ if (inTestSection && line.includes("โ")) {
+ testFailures.push(line.trim());
+ }
+ }
+
+ let message = `Structured data validation failed: ${failedCount} test(s) failed`;
+ if (testFailures.length > 0) {
+ message += ` (${testFailures.join(", ")})`;
+ }
+
this.report({
node: scriptElement,
- message: "Structured data validation failed",
+ message: message,
});
}
// Note: We don't report warnings as errors, only actual failures
} catch (error) {
+ // Extract more specific error information
+ let errorMessage = error.message;
+
+ // If there's stdout/stderr, try to extract useful information
+ if (error.stdout && error.stdout.includes("Error in jsonld parse")) {
+ const errorMatch = error.stdout.match(/Error in jsonld parse - (.+)/);
+ const parseError = errorMatch ? errorMatch[1] : "Unknown parse error";
+ errorMessage = `JSON-LD parse error: ${parseError}`;
+ } else if (error.stderr) {
+ // Use stderr if available for more detailed error info
+ errorMessage = error.stderr.trim() || errorMessage;
+ }
+
this.report({
node: scriptElement,
- message: `Structured data testing error: ${error.message}`,
+ message: `Structured data testing error: ${errorMessage}`,
});
} finally {
// Clean up temporary file