Node 22+ everywhere, LF text on Windows checkouts, CI actions v7

- Node minimum is 22 in package.json engines, the start scripts, the service installer, setup and doctor,
  matching the README, the release notes and the CI matrix (22 / 24).
- .gitattributes: CSS, HTML, JSON, Markdown and YAML check out with LF, so `node --test` on Windows matches the
  CSS rules the UI tests read; the Windows test step is no longer continue-on-error.
- CI: actions/checkout@v7 and actions/setup-node@v7 (Node 24 runtime).
This commit is contained in:
sganggs committed 2026-10-02 16:51:44 +08:00
1 parent b59aa15ee2
commit bb5c792a4d
12 files changed
+31 -26

No files matched your search

+7
View File
@@ -8,6 +8,13 @@
*.mjs text eol=lf *.mjs text eol=lf
*.js text eol=lf *.js text eol=lf
*.py text eol=lf *.py text eol=lf
# Text the tests read and match line by line (CSS rules, HTML, data, docs): LF everywhere, so a Windows checkout
# (core.autocrlf=true) runs `node --test` exactly like Linux and macOS.
*.css text eol=lf
*.html text eol=lf
*.json text eol=lf
*.md text eol=lf
*.yml text eol=lf
# binary assets # binary assets
*.png binary *.png binary
*.jpg binary *.jpg binary
+2 -4
View File
@@ -31,9 +31,9 @@ jobs:
RENDER_E2E: '0' RENDER_E2E: '0'
NO_COLOR: '1' NO_COLOR: '1'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v7
- uses: actions/setup-node@v4 - uses: actions/setup-node@v7
with: with:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
cache: npm cache: npm
@@ -46,8 +46,6 @@ jobs:
- name: Unit & integration tests - name: Unit & integration tests
run: node --test run: node --test
# Linux is the reference platform; Windows results are informative until the suite is proven there.
continue-on-error: ${{ matrix.os == 'windows-latest' }}
- name: Server smoke test (boot, /healthz, index.html, doctor) - name: Server smoke test (boot, /healthz, index.html, doctor)
shell: bash shell: bash
+1 -1
View File
@@ -27,7 +27,7 @@ Out of scope v1: matchmaking queue, training/tutorial, DIY (甄选) slots (the 4
## 1. Tech stack ## 1. Tech stack
- **Node.js ≥ 18** (tested on 22), ESM (`"type": "module"`), single dependency `ws@8`. No bundler, no TypeScript. JSDoc types where helpful. - **Node.js ≥ 22** (CI: 22 and 24), ESM (`"type": "module"`), single dependency `ws@8`. No bundler, no TypeScript. JSDoc types where helpful.
- **Server-authoritative simulation.** Clients send *intents*; the server validates, mutates state and pushes state/snapshots. - **Server-authoritative simulation.** Clients send *intents*; the server validates, mutates state and pushes state/snapshots.
- **Client:** static files, native ES modules. Vendored libs in `public/vendor/`: `pixi.min.js` (PixiJS **7.4.2** UMD, global `PIXI`), `pixi-spine.js` (**4.0.6** UMD, `PIXI.spine`), `preact.module.js` + `hooks.module.js` + `htm.module.js` (Preact 10 + htm, no build step). No CDN at runtime (LAN play must work offline). - **Client:** static files, native ES modules. Vendored libs in `public/vendor/`: `pixi.min.js` (PixiJS **7.4.2** UMD, global `PIXI`), `pixi-spine.js` (**4.0.6** UMD, `PIXI.spine`), `preact.module.js` + `hooks.module.js` + `htm.module.js` (Preact 10 + htm, no build step). No CDN at runtime (LAN play must work offline).
- **Shared code** in `shared/` is imported by both server and browser (pure ESM, no Node APIs). - **Shared code** in `shared/` is imported by both server and browser (pure ESM, no Node APIs).
+1 -1
View File
@@ -21,7 +21,7 @@
"puppeteer-core": "^23.11.1" "puppeteer-core": "^23.11.1"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=22"
} }
}, },
"node_modules/@pixi-spine/base": { "node_modules/@pixi-spine/base": {
+1 -1
View File
@@ -35,7 +35,7 @@
"launch": "node scripts/launch.mjs" "launch": "node scripts/launch.mjs"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=22"
}, },
"dependencies": { "dependencies": {
"htm": "^3.1.1", "htm": "^3.1.1",
+1 -1
View File
@@ -118,7 +118,7 @@ try {
if (-not $nodeCmd) { throw '未找到 Node.js:请先安装(winget install OpenJS.NodeJS.LTS),然后重新打开 PowerShell。' } if (-not $nodeCmd) { throw '未找到 Node.js:请先安装(winget install OpenJS.NodeJS.LTS),然后重新打开 PowerShell。' }
$nodeExe = $nodeCmd.Source $nodeExe = $nodeCmd.Source
$major = [int](((& $nodeExe -v) -replace '^v', '').Split('.')[0]) $major = [int](((& $nodeExe -v) -replace '^v', '').Split('.')[0])
if ($major -lt 18) { throw "Node.js 版本太旧(需要 18+):$(& $nodeExe -v)" } if ($major -lt 22) { throw "Node.js 版本太旧(需要 22+):$(& $nodeExe -v)" }
if ($nodeExe -like "$env:USERPROFILE*") { if ($nodeExe -like "$env:USERPROFILE*") {
Write-Host "注意:node.exe 位于用户目录($nodeExe,nvm/便携版?)。SYSTEM 账户一般也能运行它;若服务起不来,请改用 winget / 官网安装包安装的 Node.js。" -ForegroundColor Yellow Write-Host "注意:node.exe 位于用户目录($nodeExe,nvm/便携版?)。SYSTEM 账户一般也能运行它;若服务起不来,请改用 winget / 官网安装包安装的 Node.js。" -ForegroundColor Yellow
} }
+2 -2
View File
@@ -20,8 +20,8 @@ import fs from 'node:fs';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const IS_WIN = process.platform === 'win32'; const IS_WIN = process.platform === 'win32';
if (Number(process.versions.node.split('.')[0]) < 18) { if (Number(process.versions.node.split('.')[0]) < 22) {
console.error(`Node.js ${process.versions.node} 太旧,需要 18 或更高(推荐 22 LTS):https://nodejs.org/zh-cn/download`); console.error(`Node.js ${process.versions.node} 太旧,需要 22 或更高(22 / 24 LTS):https://nodejs.org/zh-cn/download`);
process.exit(1); process.exit(1);
} }
+3 -3
View File
@@ -10,7 +10,7 @@ cd /d "%~dp0.."
where node >nul 2>nul where node >nul 2>nul
if errorlevel 1 goto :nonode if errorlevel 1 goto :nonode
node -e "process.exit(Number(process.versions.node.split('.')[0])>=18?0:1)" node -e "process.exit(Number(process.versions.node.split('.')[0])>=22?0:1)"
if errorlevel 1 goto :oldnode if errorlevel 1 goto :oldnode
if not exist "node_modules\ws\package.json" ( if not exist "node_modules\ws\package.json" (
@@ -25,7 +25,7 @@ exit /b 0
:nonode :nonode
echo. echo.
echo 未找到 Node.js(需要 18 或更高,推荐 22 LTS)。Node.js not found. echo 未找到 Node.js(需要 22 或更高,22 / 24 LTS)。Node.js not found.
echo. echo.
echo 方法一:在 PowerShell 或命令提示符中运行 echo 方法一:在 PowerShell 或命令提示符中运行
echo winget install OpenJS.NodeJS.LTS echo winget install OpenJS.NodeJS.LTS
@@ -38,7 +38,7 @@ exit /b 1
:oldnode :oldnode
echo. echo.
for /f "delims=" %%v in ('node -v') do echo 当前 Node.js 版本 %%v 太旧,需要 18 或更高(推荐 22 LTS)。 for /f "delims=" %%v in ('node -v') do echo 当前 Node.js 版本 %%v 太旧,需要 22 或更高(22 / 24 LTS)。
echo 升级:winget upgrade OpenJS.NodeJS.LTS 或 https://nodejs.org/zh-cn/download echo 升级:winget upgrade OpenJS.NodeJS.LTS 或 https://nodejs.org/zh-cn/download
echo. echo.
pause pause
+3 -3
View File
@@ -31,7 +31,7 @@ function Test-Node {
$node = Test-Node $node = Test-Node
if (-not $node) { if (-not $node) {
Write-Host '未找到 Node.js(需要 18 或更高,推荐 22 LTS)。' -ForegroundColor Yellow Write-Host '未找到 Node.js(需要 22 或更高,22 / 24 LTS)。' -ForegroundColor Yellow
$winget = Get-Command winget -ErrorAction SilentlyContinue $winget = Get-Command winget -ErrorAction SilentlyContinue
if ($winget) { if ($winget) {
Write-Host '可以用 Windows 自带的 winget 安装: winget install OpenJS.NodeJS.LTS' Write-Host '可以用 Windows 自带的 winget 安装: winget install OpenJS.NodeJS.LTS'
@@ -49,8 +49,8 @@ if (-not $node) {
Pause-Exit 1 Pause-Exit 1
} }
} }
if ($node.Major -lt 18) { if ($node.Major -lt 22) {
Write-Host "Node.js $($node.Version) 太旧,需要 18 或更高:winget upgrade OpenJS.NodeJS.LTS 或 https://nodejs.org/zh-cn/download" -ForegroundColor Red Write-Host "Node.js $($node.Version) 太旧,需要 22 或更高:winget upgrade OpenJS.NodeJS.LTS 或 https://nodejs.org/zh-cn/download" -ForegroundColor Red
Pause-Exit 1 Pause-Exit 1
} }
+4 -4
View File
@@ -1,12 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# 卫戍协议:盟约 · macOS / Linux start script. Docs: docs/DEPLOY.md # 卫戍协议:盟约 · macOS / Linux start script. Docs: docs/DEPLOY.md
# scripts/start.sh [--port 3001] [--no-open] [--no-local] [--no-assets] … (arguments go to scripts/launch.mjs) # scripts/start.sh [--port 3001] [--no-open] [--no-local] [--no-assets] … (arguments go to scripts/launch.mjs)
# Checks Node.js ≥ 18, runs `npm ci` on the first run, then scripts/launch.mjs (tools/setup.mjs → server → browser). # Checks Node.js ≥ 22, runs `npm ci` on the first run, then scripts/launch.mjs (tools/setup.mjs → server → browser).
set -euo pipefail set -euo pipefail
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
if ! command -v node >/dev/null 2>&1; then if ! command -v node >/dev/null 2>&1; then
echo "未找到 Node.js(需要 18 或更高,推荐 22 LTS)。Node.js not found." echo "未找到 Node.js(需要 22 或更高,22 / 24 LTS)。Node.js not found."
if [ "$(uname -s)" = "Darwin" ]; then if [ "$(uname -s)" = "Darwin" ]; then
echo " 安装:brew install node@22 或 https://nodejs.org/zh-cn/download" echo " 安装:brew install node@22 或 https://nodejs.org/zh-cn/download"
else else
@@ -14,8 +14,8 @@ if ! command -v node >/dev/null 2>&1; then
fi fi
exit 1 exit 1
fi fi
if ! node -e "process.exit(Number(process.versions.node.split('.')[0])>=18?0:1)"; then if ! node -e "process.exit(Number(process.versions.node.split('.')[0])>=22?0:1)"; then
echo "Node.js $(node -v) 太旧,需要 18 或更高(推荐 22 LTS):https://nodejs.org/zh-cn/download" echo "Node.js $(node -v) 太旧,需要 22 或更高(22 / 24 LTS):https://nodejs.org/zh-cn/download"
exit 1 exit 1
fi fi
+1 -1
View File
@@ -164,7 +164,7 @@ async function main() {
section('运行环境'); section('运行环境');
const node = checkNode(); const node = checkNode();
row(node.ok ? (node.recommended ? 'ok' : 'warn') : 'err', 'Node.js', `v${node.version}` + (node.ok ? (node.recommended ? '' : '(推荐 22 LTS)') : `(需要 ≥ ${MIN_NODE}:https://nodejs.org/zh-cn/download)`)); row(node.ok ? (node.recommended ? 'ok' : 'warn') : 'err', 'Node.js', `v${node.version}` + (node.ok ? (node.recommended ? '' : '(推荐 22 / 24 LTS)') : `(需要 ≥ ${MIN_NODE}:https://nodejs.org/zh-cn/download)`));
const npmV = tool(IS_WIN ? 'npm.cmd' : 'npm', ['--version']); const npmV = tool(IS_WIN ? 'npm.cmd' : 'npm', ['--version']);
row(npmV ? 'ok' : 'warn', 'npm', npmV ? `v${npmV}` : '未找到(安装 Node.js 时会自带)'); row(npmV ? 'ok' : 'warn', 'npm', npmV ? `v${npmV}` : '未找到(安装 Node.js 时会自带)');
+5 -5
View File
@@ -4,7 +4,7 @@
// node tools/setup.mjs [options] // node tools/setup.mjs [options]
// //
// Steps (each one is skipped when already done, so re-running is cheap — the start scripts run it on every start): // Steps (each one is skipped when already done, so re-running is cheap — the start scripts run it on every start):
// 1. Node.js ≥ 18 check (clear message + download link otherwise). // 1. Node.js ≥ 22 check (clear message + download link otherwise).
// 2. Dependencies: `npm ci` (falls back to `npm install`) when node_modules is missing or incomplete. // 2. Dependencies: `npm ci` (falls back to `npm install`) when node_modules is missing or incomplete.
// 3. Client libraries in public/vendor (tools/vendor.mjs) when any is missing. // 3. Client libraries in public/vendor (tools/vendor.mjs) when any is missing.
// 4. Game data (data/*.json, committed) present and parseable. // 4. Game data (data/*.json, committed) present and parseable.
@@ -37,7 +37,7 @@ import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
export const MIN_NODE = 18; export const MIN_NODE = 22;
export const IS_WIN = process.platform === 'win32'; export const IS_WIN = process.platform === 'win32';
export const IS_MAC = process.platform === 'darwin'; export const IS_MAC = process.platform === 'darwin';
const NODE_URL = 'https://nodejs.org/zh-cn/download'; const NODE_URL = 'https://nodejs.org/zh-cn/download';
@@ -133,7 +133,7 @@ async function ask(question, def, timeoutMs = 60000) {
/** Node.js version check. */ /** Node.js version check. */
export function checkNode() { export function checkNode() {
const major = nodeMajor(); const major = nodeMajor();
return { ok: major >= MIN_NODE, version: process.versions.node, major, recommended: major >= 20 }; return { ok: major >= MIN_NODE, version: process.versions.node, major, recommended: major >= MIN_NODE };
} }
/** Runtime dependencies installed? */ /** Runtime dependencies installed? */
@@ -353,11 +353,11 @@ async function main() {
// 1. Node // 1. Node
const node = checkNode(); const node = checkNode();
if (!node.ok) { if (!node.ok) {
log(`${mark.err} Node.js ${node.version} 太旧:需要 ${MIN_NODE} 或更高(推荐 22 LTS)。`); log(`${mark.err} Node.js ${node.version} 太旧:需要 ${MIN_NODE} 或更高(22 / 24 LTS)。`);
log(` 下载:${NODE_URL}` + (IS_WIN ? ' 或在终端运行:winget install OpenJS.NodeJS.LTS' : IS_MAC ? ' 或:brew install node@22' : '')); log(` 下载:${NODE_URL}` + (IS_WIN ? ' 或在终端运行:winget install OpenJS.NodeJS.LTS' : IS_MAC ? ' 或:brew install node@22' : ''));
return 1; return 1;
} }
add('ok', 'Node.js', `v${node.version}${node.recommended ? '' : '(可用;推荐升级到 22 LTS)'}`); add('ok', 'Node.js', `v${node.version}${node.recommended ? '' : '(可用;推荐 22 / 24 LTS)'}`);
// 2. npm dependencies // 2. npm dependencies
let deps = checkDeps(); let deps = checkDeps();