fix: build image libraries for baseline VPS CPUs
Some checks failed
Test and deploy / Verify production build (push) Failing after 15m48s

Replace the SIMD-dependent WebAssembly fallback with source-built libvips and Sharp. Pin the libvips archive checksum, use baseline x86-64 compiler flags, retain the native binding and runtime libraries, and check JPEG/PNG/WebP processing before compilation and in the runtime image.

Lint, type checking, and content/security tests passed. Full Docker verification was stopped at the user request; production build and baseline CPU runtime verification remain pending.
This commit is contained in:
2026-09-15 23:16:54 +03:30
parent 286c9903b8
commit 1f58461967
7 changed files with 254 additions and 58 deletions

View File

@@ -1,14 +1,33 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.7
FROM node:22-alpine AS dependencies FROM node:22-alpine AS image-runtime
# Distribution libraries support baseline CPUs and dispatch optional SIMD at runtime.
RUN apk add --no-cache glib expat libjpeg-turbo libpng libwebp tiff lcms2 librsvg libstdc++
FROM image-runtime AS image-build
RUN apk add --no-cache python3 make g++ meson ninja pkgconf xz \
glib-dev expat-dev libjpeg-turbo-dev libpng-dev libwebp-dev tiff-dev lcms2-dev librsvg-dev
ADD --checksum=sha256:3c41e1d5458081bfa4a5bc54e116c46259c75c6760a18027764555632b9dda3e https://github.com/libvips/libvips/releases/download/v8.18.6/vips-8.18.6.tar.xz /tmp/vips.tar.xz
RUN if [ "$(uname -m)" = x86_64 ]; then export CFLAGS='-O2 -march=x86-64 -mtune=generic' CXXFLAGS='-O2 -march=x86-64 -mtune=generic'; fi \
&& mkdir /tmp/vips && tar -xJf /tmp/vips.tar.xz -C /tmp/vips --strip-components=1 \
&& meson setup /tmp/vips/build /tmp/vips --prefix=/opt/vips --libdir=lib \
--buildtype=release --auto-features=disabled \
-Dhighway=disabled -Dorc=disabled -Dcplusplus=true -Dexamples=false \
-Djpeg=enabled -Dpng=enabled -Dwebp=enabled -Dtiff=enabled -Dlcms=enabled -Drsvg=enabled -Dzlib=enabled \
&& meson compile -C /tmp/vips/build -j 2 \
&& meson install -C /tmp/vips/build
FROM image-build AS dependencies
WORKDIR /app WORKDIR /app
# npm may build the native SQLite binding when no matching prebuild is available.
RUN apk add --no-cache python3 make g++
ARG NPM_REGISTRY=https://package-mirror.liara.ir/repository/npm/ ARG NPM_REGISTRY=https://package-mirror.liara.ir/repository/npm/
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1 \
PKG_CONFIG_PATH=/opt/vips/lib/pkgconfig \
LD_LIBRARY_PATH=/opt/vips/lib \
npm_config_nodedir=/usr/local
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN --mount=type=cache,id=portfolio-npm-node22,target=/root/.npm,sharing=locked \ RUN --mount=type=cache,id=portfolio-npm-node22,target=/root/.npm,sharing=locked \
@@ -17,8 +36,12 @@ RUN --mount=type=cache,id=portfolio-npm-node22,target=/root/.npm,sharing=locked
--replace-registry-host=registry.npmjs.org \ --replace-registry-host=registry.npmjs.org \
--include=optional --include=optional
# Compile Sharp against our baseline libvips, not its x86-64-v2 prebuilt binary.
RUN if [ "$(uname -m)" = x86_64 ]; then export CFLAGS='-O2 -march=x86-64 -mtune=generic' CXXFLAGS='-O2 -march=x86-64 -mtune=generic'; fi \
&& npm explore sharp -- npm run build
COPY scripts/check-image-runtime.mjs ./scripts/check-image-runtime.mjs COPY scripts/check-image-runtime.mjs ./scripts/check-image-runtime.mjs
RUN node scripts/check-image-runtime.mjs RUN node scripts/check-image-runtime.mjs --require-source
FROM dependencies AS verification FROM dependencies AS verification
@@ -29,14 +52,15 @@ RUN --mount=type=cache,id=portfolio-next-node22,target=/app/.next/cache,sharing=
npm run build npm run build
RUN npm test RUN npm test
FROM node:22-alpine AS runtime FROM image-runtime AS runtime
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production \ ENV NODE_ENV=production \
NEXT_TELEMETRY_DISABLED=1 \ NEXT_TELEMETRY_DISABLED=1 \
HOSTNAME=0.0.0.0 \ HOSTNAME=0.0.0.0 \
PORT=3000 \ PORT=3000 \
DATA_DIR=/data DATA_DIR=/data \
LD_LIBRARY_PATH=/opt/vips/lib
RUN addgroup --system --gid 1001 nodejs \ RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs && adduser --system --uid 1001 nextjs
@@ -44,6 +68,11 @@ RUN addgroup --system --gid 1001 nodejs \
COPY --from=verification --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=verification --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=verification --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=verification --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=verification --chown=nextjs:nodejs /app/public ./public COPY --from=verification --chown=nextjs:nodejs /app/public ./public
COPY --from=image-build /opt/vips/lib /opt/vips/lib
# Explicitly retain the custom binding even if Next's file tracer misses it.
COPY --from=dependencies --chown=nextjs:nodejs /app/node_modules/sharp/src/build/Release/*.node ./node_modules/sharp/src/build/Release/
COPY scripts/check-image-runtime.mjs ./scripts/check-image-runtime.mjs
RUN node scripts/check-image-runtime.mjs --require-source
RUN mkdir -p .next/cache /data && chown -R nextjs:nodejs .next/cache /data RUN mkdir -p .next/cache /data && chown -R nextjs:nodejs .next/cache /data
USER nextjs USER nextjs

View File

@@ -57,10 +57,12 @@ Drizzle Kit, npm installs, or a compiler at runtime. Startup applies the committ
SQL migrations and seeds samples only when the database is empty. `/api/health` SQL migrations and seeds samples only when the database is empty. `/api/health`
checks database readiness without rendering the portfolio. checks database readiness without rendering the portfolio.
Sharp uses native image processing where supported and includes a WebAssembly The Docker build compiles libvips and Sharp from source for baseline CPUs. This
fallback for servers without the required x86-64 CPU features. The fallback supports VPS hosts that lack the features required by Sharp's prebuilt native
preserves uploads and image optimization, but can use more CPU time. Keep and WebAssembly binaries. The first build takes longer; Docker caches the image
`@img/sharp-wasm32` at the same version as `sharp` when upgrading. libraries for later application changes. Image processing is checked before
Next.js compilation and again in the final runtime image. When upgrading Sharp,
keep the libvips source version in the Dockerfile at or above Sharp's minimum.
Use one application instance with a local filesystem. Do not mount the SQLite Use one application instance with a local filesystem. Do not mount the SQLite
database over NFS/SMB, use an ephemeral serverless disk, or run multiple replicas database over NFS/SMB, use an ephemeral serverless disk, or run multiple replicas

View File

@@ -5,9 +5,7 @@ const nextConfig = {
outputFileTracingIncludes: { outputFileTracingIncludes: {
'/*': [ '/*': [
'./migrations/**/*', './migrations/**/*',
'./node_modules/@img/sharp-wasm32/**/*', './node_modules/sharp/src/build/Release/*.node',
'./node_modules/@emnapi/runtime/**/*',
'./node_modules/tslib/**/*',
], ],
}, },
outputFileTracingExcludes: { outputFileTracingExcludes: {

199
package-lock.json generated
View File

@@ -8,7 +8,6 @@
"name": "portfolio-v2-antigravity", "name": "portfolio-v2-antigravity",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@img/sharp-wasm32": "0.35.4",
"better-auth": "^1.7.5", "better-auth": "^1.7.5",
"better-sqlite3": "^13.0.3", "better-sqlite3": "^13.0.3",
"drizzle-orm": "^0.45.2", "drizzle-orm": "^0.45.2",
@@ -33,6 +32,8 @@
"esbuild": "^0.28.2", "esbuild": "^0.28.2",
"eslint": "^9.39.5", "eslint": "^9.39.5",
"eslint-config-next": "^16.3.4", "eslint-config-next": "^16.3.4",
"node-addon-api": "8.9.2",
"node-gyp": "12.4.0",
"tsx": "^4.23.13", "tsx": "^4.23.13",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
@@ -457,6 +458,7 @@
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/@emnapi/runtime/-/runtime-1.11.3.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/@emnapi/runtime/-/runtime-1.11.3.tgz",
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
@@ -1610,6 +1612,7 @@
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz",
"integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==",
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.11.3" "@emnapi/runtime": "^1.11.3"
}, },
@@ -1696,6 +1699,19 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -2734,6 +2750,16 @@
"dev": true, "dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/abbrev": {
"version": "4.0.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/abbrev/-/abbrev-4.0.0.tgz",
"integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.18.0", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
@@ -3348,6 +3374,16 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/client-only": { "node_modules/client-only": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/client-only/-/client-only-0.0.1.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/client-only/-/client-only-0.0.1.tgz",
@@ -3730,6 +3766,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.24.2", "version": "1.24.2",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/es-abstract/-/es-abstract-1.24.2.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/es-abstract/-/es-abstract-1.24.2.tgz",
@@ -4486,6 +4532,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/exponential-backoff": {
"version": "3.1.3",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4838,6 +4891,13 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/has-bigints": { "node_modules/has-bigints": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/has-bigints/-/has-bigints-1.1.0.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -5728,6 +5788,29 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minizlib": {
"version": "3.1.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/ms/-/ms-2.1.3.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/ms/-/ms-2.1.3.tgz",
@@ -5882,6 +5965,57 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/node-gyp": {
"version": "12.4.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/node-gyp/-/node-gyp-12.4.0.tgz",
"integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==",
"dev": true,
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.0",
"exponential-backoff": "^3.1.1",
"graceful-fs": "^4.2.6",
"nopt": "^9.0.0",
"proc-log": "^6.0.0",
"semver": "^7.3.5",
"tar": "^7.5.4",
"tinyglobby": "^0.2.12",
"undici": "^6.25.0",
"which": "^6.0.0"
},
"bin": {
"node-gyp": "bin/node-gyp.js"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/node-gyp/node_modules/isexe": {
"version": "4.0.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/isexe/-/isexe-4.0.0.tgz",
"integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=20"
}
},
"node_modules/node-gyp/node_modules/which": {
"version": "6.0.1",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/which/-/which-6.0.1.tgz",
"integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^4.0.0"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.54", "version": "2.0.54",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/node-releases/-/node-releases-2.0.54.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/node-releases/-/node-releases-2.0.54.tgz",
@@ -5892,6 +6026,22 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/nopt": {
"version": "9.0.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/nopt/-/nopt-9.0.0.tgz",
"integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==",
"dev": true,
"license": "ISC",
"dependencies": {
"abbrev": "^4.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/object-assign/-/object-assign-4.1.1.tgz",
@@ -6220,6 +6370,16 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/proc-log": {
"version": "6.1.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/proc-log/-/proc-log-6.1.0.tgz",
"integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/prop-types": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/prop-types/-/prop-types-15.8.1.tgz",
@@ -6948,6 +7108,33 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/tar": {
"version": "7.5.22",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/tar/-/tar-7.5.22.tgz",
"integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tar/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/three": { "node_modules/three": {
"version": "0.180.0", "version": "0.180.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/three/-/three-0.180.0.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/three/-/three-0.180.0.tgz",
@@ -7227,6 +7414,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/undici": {
"version": "6.28.1",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/undici/-/undici-6.28.1.tgz",
"integrity": "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://package-mirror.liara.ir/artifactory/api/npm/npm/undici-types/-/undici-types-6.21.0.tgz",

View File

@@ -16,7 +16,6 @@
"maintenance": "tsx --env-file-if-exists=.env scripts/maintenance.ts" "maintenance": "tsx --env-file-if-exists=.env scripts/maintenance.ts"
}, },
"dependencies": { "dependencies": {
"@img/sharp-wasm32": "0.35.4",
"better-auth": "^1.7.5", "better-auth": "^1.7.5",
"better-sqlite3": "^13.0.3", "better-sqlite3": "^13.0.3",
"drizzle-orm": "^0.45.2", "drizzle-orm": "^0.45.2",
@@ -41,6 +40,8 @@
"esbuild": "^0.28.2", "esbuild": "^0.28.2",
"eslint": "^9.39.5", "eslint": "^9.39.5",
"eslint-config-next": "^16.3.4", "eslint-config-next": "^16.3.4",
"node-addon-api": "8.9.2",
"node-gyp": "12.4.0",
"tsx": "^4.23.13", "tsx": "^4.23.13",
"typescript": "^5.7.3" "typescript": "^5.7.3"
}, },

View File

@@ -2,24 +2,19 @@ import { createRequire } from 'node:module';
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
// Check the fallback directly: Sharp 0.35.4 can hide its load error behind
// an unrelated `err.code.endsWith` exception on unsupported native CPUs.
try {
require('@img/sharp-wasm32/sharp.node');
console.log('Sharp WebAssembly binding loaded.');
} catch (error) {
console.error('Sharp WebAssembly binding failed to load:', error);
process.exit(1);
}
try { try {
const { default: sharp } = await import('sharp'); const { default: sharp } = await import('sharp');
const input = await sharp({ create: { width: 8, height: 8, channels: 3, background: '#336699' } }).webp().toBuffer(); if (process.argv.includes('--require-source') && !Object.keys(require.cache).some(file => /sharp[/\\]src[/\\]build[/\\]Release[/\\].+\.node$/.test(file))) {
const metadata = await sharp(input).metadata(); throw new Error('Expected the source-built Sharp binding; refusing to use a prebuilt fallback');
if (metadata.format !== 'webp' || metadata.width !== 8 || metadata.height !== 8) {
throw new Error('Image encode/decode verification failed');
} }
console.log('Sharp ESM import and image processing passed.'); for (const format of ['jpeg', 'png', 'webp']) {
const input = await sharp({ create: { width: 8, height: 8, channels: 3, background: '#336699' } }).toFormat(format).toBuffer();
const metadata = await sharp(input).metadata();
if (metadata.format !== format || metadata.width !== 8 || metadata.height !== 8) {
throw new Error(`${format} encode/decode verification failed`);
}
}
console.log(`Sharp ${sharp.versions.sharp}, libvips ${sharp.versions.vips}: JPEG, PNG and WebP passed.`);
} catch (error) { } catch (error) {
console.error('Sharp image runtime failed:', error); console.error('Sharp image runtime failed:', error);
process.exit(1); process.exit(1);

View File

@@ -1,26 +0,0 @@
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import test from 'node:test';
test('image processing falls back to WebAssembly without native CPU support', () => {
// Isolate the native-binding simulation from the rest of the test process.
const result = spawnSync(process.execPath, ['-e', `
const Module = require('node:module');
const original = Module._load;
Module._load = function (id, ...args) {
if (id.endsWith('.node') && !id.includes('wasm32')) {
const error = new Error('Simulated unavailable native binding');
error.code = 'MODULE_NOT_FOUND';
throw error;
}
return original.call(this, id, ...args);
};
const sharp = require('sharp');
(async () => {
const image = await sharp({ create: { width: 8, height: 8, channels: 3, background: '#336699' } }).webp().toBuffer();
const metadata = await sharp(image).metadata();
if (metadata.format !== 'webp' || metadata.width !== 8 || metadata.height !== 8) throw new Error('Image round trip failed');
})().catch(error => { console.error(error); process.exitCode = 1; });
`], { encoding: 'utf8', timeout: 30000 });
assert.equal(result.status, 0, result.stderr || result.error?.message);
});