From 2c43fe7784bf4715700cab0b4497854336a91d31 Mon Sep 17 00:00:00 2001 From: colin Date: Sun, 6 Jul 2025 18:36:59 -0400 Subject: [PATCH] Fix duplicate footer issues and remove npm dependency --- .cursorignore | 3 +- docker/package-lock.json | 25 + docker/resume/Caddyfile | 4 +- docker/resume/Caddyfile.local | 2 +- docker/resume/Dockerfile | 48 +- docker/resume/Dockerfile.production | 2 +- docker/resume/csv-tool-output.html | 2 +- docker/resume/generate-sitemap.sh | 61 + docker/resume/includes/header.html | 20 +- docker/resume/index-with-includes.html | 2 +- docker/resume/index.html | 6 +- docker/resume/one-pager-tools/csv-tool.html | 2 +- docker/resume/one-pager-tools/template.html | 2 +- .../one-pager-tools/tool-with-includes.html | 2 +- docker/resume/sitemap.xml | 99 + docker/resume/stories/airport-dns.html | 4 +- docker/resume/stories/app-development.html | 4 +- docker/resume/stories/athion-turnaround.html | 4 +- docker/resume/stories/fawe-plotsquared.html | 4 +- .../resume/stories/healthcare-platform.html | 4 +- docker/resume/stories/index.html | 141 +- docker/resume/stories/motherboard-repair.html | 4 +- docker/resume/stories/nitric-leadership.html | 4 +- .../resume/stories/open-source-success.html | 4 +- docker/resume/stories/showerloop.html | 4 +- docker/resume/stories/stories.css | 8 +- docker/resume/stories/stories.css.fixed | 216 ++ .../resume/stories/story-with-includes.html | 4 +- docker/resume/stories/template-story.html | 4 +- docker/resume/stories/viperwire.html | 4 +- docker/resume/stories/web-design-java.html | 4 +- docker/resume/stories/wordpress-security.html | 4 +- docker/resume/stories/youtube-game-dev.html | 4 +- docker/resume/template-with-includes.html | 2 +- docker/resume/tests/test-run.log | 19 + docker/resume/update-all.sh | 80 + docker/resume/update-nav-from-sitemap.js | 133 ++ docker/resume/update-stories-page.js | 185 ++ package-lock.json | 1732 +++++++---------- playwright.config.js | 36 +- tests/accessibility.test.js | 232 +-- tests/accessibility/README.md | 86 +- .../accessibility/generate-detailed-report.js | 537 +++++ tests/accessibility/generate-report.js | 212 ++ tests/accessibility/manual-checklist.md | 138 +- tests/accessibility/pa11y-test.sh | 136 +- tests/accessibility/playwright-axe.js | 122 +- .../accessibility/run-accessibility-tests.sh | 4 +- tests/integration/sitemap-test.sh | 67 + tests/pre-test-setup.sh | 55 +- tests/run-all-tests.sh | 150 +- 51 files changed, 2895 insertions(+), 1736 deletions(-) create mode 100644 docker/package-lock.json create mode 100755 docker/resume/generate-sitemap.sh create mode 100644 docker/resume/sitemap.xml create mode 100644 docker/resume/stories/stories.css.fixed create mode 100644 docker/resume/tests/test-run.log create mode 100755 docker/resume/update-all.sh create mode 100755 docker/resume/update-nav-from-sitemap.js create mode 100755 docker/resume/update-stories-page.js create mode 100644 tests/accessibility/generate-detailed-report.js create mode 100644 tests/accessibility/generate-report.js create mode 100755 tests/integration/sitemap-test.sh diff --git a/.cursorignore b/.cursorignore index 4d390ae..706dcbb 100644 --- a/.cursorignore +++ b/.cursorignore @@ -8,4 +8,5 @@ /README.md /stack.production.yml /stack.staging.yml -/tests/ \ No newline at end of file +# /tests/ +Dockerfile.production \ No newline at end of file diff --git a/docker/package-lock.json b/docker/package-lock.json new file mode 100644 index 0000000..6d0d1da --- /dev/null +++ b/docker/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "docker", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "docker", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "xmldom": "^0.6.0" + } + }, + "node_modules/xmldom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", + "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + } + } +} diff --git a/docker/resume/Caddyfile b/docker/resume/Caddyfile index 697179c..4d58280 100644 --- a/docker/resume/Caddyfile +++ b/docker/resume/Caddyfile @@ -29,7 +29,7 @@ colinknapp.com { Cache-Control "public, max-age=31536000, immutable" # CSP with hashes for scripts and styles - Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-fOEWMJmrMxKbP5wElIXmDNUlfs6BSn+E9zt81T0Rysg=' 'sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-JR8sYN1/jgctBktEsjejl175usnuJQ+LimW18BWyL8I=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-efXJB9ojE48KDEisFG5s+pGha1fH1bZA/IKW/ZKrL50=' 'sha256-5Lrk4RP6+4oP0Dbe2qVepxbZ0tYjXoWQHz55YlbGXFk=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" + Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-uTJNJlctGr5GxR5DKnz1Ex31vH0TR93OFGloxbHe65g=' 'sha256-fOEWMJmrMxKbP5wElIXmDNUlfs6BSn+E9zt81T0Rysg=' 'sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-UUDFHb6NI63nBRS2EmyJq4giwjTQGYPq7uSTB4UQnPc=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=' 'sha256-8CNR2aPoRsO94LHwXXZzxijfMf15BfwUewt8hvVbPcE='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-efXJB9ojE48KDEisFG5s+pGha1fH1bZA/IKW/ZKrL50=' 'sha256-r0ECPtfllGARVL3R4rbe8FsQgyNZPyqJ6vkvvwXQpqM=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" } # Handle 404s @@ -79,7 +79,7 @@ colinknapp.com { Cache-Control "public, max-age=31536000, immutable" # CSP with hashes for scripts and styles - Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-fOEWMJmrMxKbP5wElIXmDNUlfs6BSn+E9zt81T0Rysg=' 'sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-JR8sYN1/jgctBktEsjejl175usnuJQ+LimW18BWyL8I=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-efXJB9ojE48KDEisFG5s+pGha1fH1bZA/IKW/ZKrL50=' 'sha256-5Lrk4RP6+4oP0Dbe2qVepxbZ0tYjXoWQHz55YlbGXFk=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" + Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-uTJNJlctGr5GxR5DKnz1Ex31vH0TR93OFGloxbHe65g=' 'sha256-fOEWMJmrMxKbP5wElIXmDNUlfs6BSn+E9zt81T0Rysg=' 'sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-UUDFHb6NI63nBRS2EmyJq4giwjTQGYPq7uSTB4UQnPc=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=' 'sha256-8CNR2aPoRsO94LHwXXZzxijfMf15BfwUewt8hvVbPcE='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-efXJB9ojE48KDEisFG5s+pGha1fH1bZA/IKW/ZKrL50=' 'sha256-r0ECPtfllGARVL3R4rbe8FsQgyNZPyqJ6vkvvwXQpqM=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" } # Handle 404s diff --git a/docker/resume/Caddyfile.local b/docker/resume/Caddyfile.local index 4232461..a377b74 100644 --- a/docker/resume/Caddyfile.local +++ b/docker/resume/Caddyfile.local @@ -39,6 +39,6 @@ Cache-Control "public, max-age=31536000, immutable" # CSP with hashes for scripts and styles - Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-fOEWMJmrMxKbP5wElIXmDNUlfs6BSn+E9zt81T0Rysg=' 'sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-JR8sYN1/jgctBktEsjejl175usnuJQ+LimW18BWyL8I=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-efXJB9ojE48KDEisFG5s+pGha1fH1bZA/IKW/ZKrL50=' 'sha256-5Lrk4RP6+4oP0Dbe2qVepxbZ0tYjXoWQHz55YlbGXFk=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" + Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-uTJNJlctGr5GxR5DKnz1Ex31vH0TR93OFGloxbHe65g=' 'sha256-fOEWMJmrMxKbP5wElIXmDNUlfs6BSn+E9zt81T0Rysg=' 'sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-UUDFHb6NI63nBRS2EmyJq4giwjTQGYPq7uSTB4UQnPc=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=' 'sha256-8CNR2aPoRsO94LHwXXZzxijfMf15BfwUewt8hvVbPcE='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-efXJB9ojE48KDEisFG5s+pGha1fH1bZA/IKW/ZKrL50=' 'sha256-r0ECPtfllGARVL3R4rbe8FsQgyNZPyqJ6vkvvwXQpqM=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" } } diff --git a/docker/resume/Dockerfile b/docker/resume/Dockerfile index 57f3abe..dde1a56 100644 --- a/docker/resume/Dockerfile +++ b/docker/resume/Dockerfile @@ -1,43 +1,21 @@ -FROM caddy:2-alpine +FROM caddy:2.7-alpine -# Install required tools for hash calculation and CSP updates -RUN apk add --no-cache bash coreutils findutils grep sed xxd perl gawk - -# Copy update scripts first -COPY update-csp-hashes.sh /srv/update-csp-hashes.sh -COPY caddy.sh /srv/caddy.sh - -# Copy Caddyfile and static content -COPY Caddyfile /etc/caddy/Caddyfile -# Also copy to /srv for compatibility with the script -COPY Caddyfile /srv/Caddyfile -COPY index.html /srv/ -COPY theme.js /srv/ -COPY utils.js /srv/ -COPY styles.css /srv/ -COPY favicon.ico /srv/ -COPY includes.js /srv/ -COPY papaparse.min.js /srv/ - -# Copy one-pager-tools directory -COPY one-pager-tools /srv/one-pager-tools/ - -# Copy includes directory -COPY includes/ /srv/includes/ - -# Copy stories directory -COPY stories/ /srv/stories/ +# Install dependencies +RUN apk add --no-cache nodejs bash # Set working directory WORKDIR /srv -# Run the update-csp-hashes.sh script to update CSP hashes -RUN chmod +x /srv/caddy.sh /srv/update-csp-hashes.sh && \ - cd /srv && \ - ./update-csp-hashes.sh +# Copy website files +COPY . /srv -# Expose port 8080 +# Run all update scripts (sitemap, navigation, stories, CSP hashes, accessibility fixes) +RUN cd /srv && \ + chmod +x update-all.sh && \ + ./update-all.sh + +# Expose port EXPOSE 8080 -# Run Caddy -CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"] +# Start Caddy with the local Caddyfile +CMD ["caddy", "run", "--config", "/srv/Caddyfile.local"] diff --git a/docker/resume/Dockerfile.production b/docker/resume/Dockerfile.production index d3337ab..3fcf77b 100644 --- a/docker/resume/Dockerfile.production +++ b/docker/resume/Dockerfile.production @@ -1 +1 @@ -FROM git.nixc.us/colin/resume:staging \ No newline at end of file +FROM git.nixc.us/nixc/resume:staging diff --git a/docker/resume/csv-tool-output.html b/docker/resume/csv-tool-output.html index 074ea36..f3b3535 100644 --- a/docker/resume/csv-tool-output.html +++ b/docker/resume/csv-tool-output.html @@ -52,7 +52,7 @@ } - +
diff --git a/docker/resume/generate-sitemap.sh b/docker/resume/generate-sitemap.sh new file mode 100755 index 0000000..cc52b4e --- /dev/null +++ b/docker/resume/generate-sitemap.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# ===================================================================== +# generate-sitemap.sh - Generate sitemap.xml for the website +# ===================================================================== +# This script generates a sitemap.xml file for the website +# It should be run after any content updates +# ===================================================================== + +set -e + +echo "Generating sitemap.xml..." + +# Directory containing the files +BASE_DIR="$(pwd)" +DOMAIN="https://resume.example.com" # Replace with your actual domain in production + +# Get current date in ISO 8601 format +CURRENT_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S+00:00") + +# Create sitemap header +cat > "$BASE_DIR/sitemap.xml" << EOF + + +EOF + +# Find all HTML files and add them to sitemap +find "$BASE_DIR" -name "*.html" -type f | sort | while read -r html_file; do + # Skip files in includes directory and template files + if [[ "$html_file" == *"/includes/"* ]] || [[ "$html_file" == *"-with-includes.html" ]] || [[ "$html_file" == *"template"* ]]; then + continue + fi + + # Get relative path from base directory + rel_path="${html_file#$BASE_DIR/}" + + # Skip csv-tool-output.html as it's dynamically generated + if [[ "$rel_path" == "csv-tool-output.html" ]]; then + continue + fi + + # Create URL + url="$DOMAIN/$rel_path" + + # Add URL to sitemap + cat >> "$BASE_DIR/sitemap.xml" << EOF + + $url + $CURRENT_DATE + monthly + 0.8 + +EOF +done + +# Close sitemap +cat >> "$BASE_DIR/sitemap.xml" << EOF + +EOF + +echo "Sitemap generated at $BASE_DIR/sitemap.xml" +echo "Total URLs: $(grep -c "" "$BASE_DIR/sitemap.xml")" diff --git a/docker/resume/includes/header.html b/docker/resume/includes/header.html index 0670256..e6393fa 100644 --- a/docker/resume/includes/header.html +++ b/docker/resume/includes/header.html @@ -15,13 +15,19 @@