Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ jobs:
echo "-> Save PM2 process list"
pm2 save

# PM2 reporting "online" only means the process exists, which is not
# the same as it serving. On 2026-08-17 a bundle missing part of
# @swc/helpers died on startup without ever binding the port: PM2
# showed online, this workflow reported success, and the site
# returned 503 for 40 minutes. Ask the port, not the supervisor.
echo "-> Health check"
code=""
for i in $(seq 1 20); do
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://127.0.0.1:3008/ || true)
[ "$code" = "200" ] && { echo "Serving after ${i} attempt(s)."; break; }
sleep 3
done
if [ "$code" != "200" ]; then
echo "::error::Web process is not serving on :3008 (last status: ${code:-none})"
pm2 logs msk-forms-web --lines 40 --nostream --err || true
exit 1
fi

echo "Deploy finished"

- name: Notify Discord (optional)
Expand Down
41 changes: 41 additions & 0 deletions apps/web/scripts/copy-standalone-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
// into the bundle under Turbopack — at runtime sharp then fails with
// "Could not load the sharp module … libvips-cpp.so… cannot open shared object
// file". So we copy sharp + its platform `@img/*` binaries in explicitly.
//
// Same story for `@swc/helpers`, whose ESM half 16.3.1 leaves behind. Both are
// the same lesson: a green build says nothing about whether the bundle it
// produced can actually boot, so anything Next declines to trace has to be put
// back here by hand.
import { cpSync, existsSync, readdirSync, realpathSync, rmSync } from "node:fs";
import { basename, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
Expand Down Expand Up @@ -89,11 +94,47 @@ function copySharp() {
}
}

/**
* Restore `@swc/helpers` in the bundle's pnpm store.
*
* Next 16.3.1 traces only the `cjs/` half of the package into
* `output: standalone`, but its own `require-hook` loads the ESM entry point.
* The bundle therefore starts and then dies on the first request that needs a
* helper: "Cannot find module …/@swc/helpers/esm/_interop_require_default.js",
* raised as an unhandledRejection. The process stays alive without ever binding
* the port, so PM2 reports it as online while every request 503s. This took the
* site down on 2026-08-17 (next 16.2.12 -> 16.3.1).
*
* Copy the whole package rather than just the missing directory, so a future
* change to which half gets traced cannot reintroduce this. Version-matched per
* store entry: mixing the `esm/` of one version into another happens to work
* but is not something to ship.
*/
function copySwcHelpers() {
const standalonePnpm = join(webRoot, ".next", "standalone", "node_modules", ".pnpm");
const sourcePnpm = join(webRoot, "..", "..", "node_modules", ".pnpm");
if (!existsSync(standalonePnpm) || !existsSync(sourcePnpm)) return;

for (const entry of readdirSync(standalonePnpm)) {
if (!entry.startsWith("@swc+helpers@")) continue;
const src = join(sourcePnpm, entry, "node_modules", "@swc", "helpers");
const dst = join(standalonePnpm, entry, "node_modules", "@swc", "helpers");
if (!existsSync(src)) {
console.log(`${entry}: no source copy found, leaving the traced one alone.`);
continue;
}
rmSync(dst, { recursive: true, force: true });
cpSync(src, dst, { recursive: true, dereference: true });
console.log(`restored ${entry} -> ${dst}`);
}
}

if (!existsSync(standaloneWeb)) {
console.log("No standalone output found; skipping standalone asset copy.");
} else {
mirror(join(".next", "static"), join(standaloneWeb, ".next", "static"));
mirror("public", join(standaloneWeb, "public"));
copySharp();
copySwcHelpers();
console.log("Standalone assets ready.");
}