From a941742ebd7c6ea98ade5db5e4a28ff9b7c6657b Mon Sep 17 00:00:00 2001 From: Tynab Date: Mon, 29 Jun 2026 21:02:03 +0700 Subject: [PATCH 1/4] fix: sync lockfile for Docker npm ci --- package-lock.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/package-lock.json b/package-lock.json index 1229175..6078d13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17293,6 +17293,23 @@ } } }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", From ab731db92befb66b715dd3e4b820e7931c753651 Mon Sep 17 00:00:00 2001 From: Tynab Date: Mon, 29 Jun 2026 21:04:31 +0700 Subject: [PATCH 2/4] fix: serve Docker build with nginx --- Dockerfile | 17 ++++++++++------- nginx.conf | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 nginx.conf diff --git a/Dockerfile b/Dockerfile index e72a90b..9c33e34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,18 @@ -# Tóm tắt: Image chạy portfolio React bằng dependency đúng từ package-lock. -FROM node:22-alpine +# Tóm tắt: Build portfolio React thành static bundle rồi serve bằng Nginx. +FROM node:22-alpine AS build WORKDIR /app -ENV HOST=0.0.0.0 -ENV PATH=/app/node_modules/.bin:$PATH - COPY package.json package-lock.json ./ RUN npm ci COPY . . +RUN npm run build + +FROM nginx:1.27-alpine + +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/build /usr/share/nginx/html -EXPOSE 3000 -CMD ["npm", "start"] +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..b3fb40e --- /dev/null +++ b/nginx.conf @@ -0,0 +1,17 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff2?)$ { + expires 7d; + add_header Cache-Control "public"; + try_files $uri =404; + } +} From 1ee74890020ab81036353943f5f8eff42c5566f5 Mon Sep 17 00:00:00 2001 From: Tynab Date: Mon, 29 Jun 2026 21:14:31 +0700 Subject: [PATCH 3/4] fix: fail Docker build on unresolved LFS assets --- Dockerfile | 1 + scripts/verify-lfs-assets.js | 61 ++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 scripts/verify-lfs-assets.js diff --git a/Dockerfile b/Dockerfile index 9c33e34..3a533a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ COPY package.json package-lock.json ./ RUN npm ci COPY . . +RUN node scripts/verify-lfs-assets.js RUN npm run build FROM nginx:1.27-alpine diff --git a/scripts/verify-lfs-assets.js b/scripts/verify-lfs-assets.js new file mode 100644 index 0000000..ea2dffb --- /dev/null +++ b/scripts/verify-lfs-assets.js @@ -0,0 +1,61 @@ +const fs = require("fs"); +const path = require("path"); + +const assetRoots = ["public", path.join("src", "assests")]; +const binaryExtensions = new Set([ + ".eot", + ".gif", + ".ico", + ".jpg", + ".jpeg", + ".png", + ".svg", + ".ttf", + ".webp", + ".woff", + ".woff2", +]); +const lfsPointerMarker = "version https://git-lfs.github.com/spec/v1"; + +function walkFiles(root) { + if (!fs.existsSync(root)) { + return []; + } + + const entries = fs.readdirSync(root, { withFileTypes: true }); + return entries.flatMap((entry) => { + const currentPath = path.join(root, entry.name); + return entry.isDirectory() ? walkFiles(currentPath) : [currentPath]; + }); +} + +const unresolvedAssets = assetRoots + .flatMap(walkFiles) + .filter((filePath) => + binaryExtensions.has(path.extname(filePath).toLowerCase()) + ) + .filter((filePath) => { + const handle = fs.openSync(filePath, "r"); + try { + const buffer = Buffer.alloc(lfsPointerMarker.length); + fs.readSync(handle, buffer, 0, buffer.length, 0); + return buffer.toString("utf8") === lfsPointerMarker; + } finally { + fs.closeSync(handle); + } + }); + +if (unresolvedAssets.length > 0) { + console.error( + "Git LFS assets are not resolved. Run `git lfs install` and `git lfs pull` before building Docker." + ); + unresolvedAssets + .slice(0, 20) + .forEach((filePath) => console.error(`- ${filePath}`)); + if (unresolvedAssets.length > 20) { + console.error(`...and ${unresolvedAssets.length - 20} more files.`); + } + process.exit(1); +} + +console.log("Git LFS asset check passed."); From 5a073b26381c8b1d676169d06f22c92bba67edb0 Mon Sep 17 00:00:00 2001 From: Tynab Date: Mon, 29 Jun 2026 21:17:11 +0700 Subject: [PATCH 4/4] fix: refresh cached skill assets --- nginx.conf | 5 +++++ src/components/softwareSkills/SoftwareSkill.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/nginx.conf b/nginx.conf index b3fb40e..2f0c4c7 100644 --- a/nginx.conf +++ b/nginx.conf @@ -9,6 +9,11 @@ server { try_files $uri $uri/ /index.html; } + location ^~ /skills/ { + add_header Cache-Control "no-cache"; + try_files $uri =404; + } + location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff2?)$ { expires 7d; add_header Cache-Control "public"; diff --git a/src/components/softwareSkills/SoftwareSkill.js b/src/components/softwareSkills/SoftwareSkill.js index a5e3ff3..84e922c 100644 --- a/src/components/softwareSkills/SoftwareSkill.js +++ b/src/components/softwareSkills/SoftwareSkill.js @@ -2,6 +2,8 @@ import React from "react"; import "./SoftwareSkill.css"; import { OverlayTrigger, Tooltip } from "react-bootstrap"; +const skillAssetVersion = "2026-06-29-lfs-refresh"; + // Tóm tắt: Render lưới kỹ năng phần mềm từ cấu hình, hỗ trợ cả Iconify và ảnh tĩnh. class SoftwareSkill extends React.Component { render() { @@ -35,7 +37,7 @@ class SoftwareSkill extends React.Component { {logo.skillName} )}