🐛 fix(workflow): manage Node versions with fnm

This commit is contained in:
csh
2026-08-13 17:33:55 +08:00
parent f90faa10c3
commit fac69aafbd
6 changed files with 126 additions and 5 deletions
+25
View File
@@ -26,6 +26,8 @@ env:
SOURCE_SHA: ${{ github.event.workflow_run.head_sha }} SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
SOURCE_EVENT: ${{ github.event.workflow_run.event }} SOURCE_EVENT: ${{ github.event.workflow_run.event }}
COMMIT_LINT_REQUIRE_EMOJI: "1" COMMIT_LINT_REQUIRE_EMOJI: "1"
FNM_BIN: "/data/bin/fnm"
FNM_DIR: "/data/fnm"
jobs: jobs:
checks: checks:
@@ -131,6 +133,29 @@ jobs:
echo "========================================" echo "========================================"
cd "$REPO_DIR" cd "$REPO_DIR"
node_version=$(tr -d '[:space:]' < .node-version)
if ! [[ "$node_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo ".node-version 必须是完整的 Node.js 版本号: $node_version" >&2
exit 1
fi
if [ ! -x "$FNM_BIN" ]; then
echo "Prepare 未安装 fnm: $FNM_BIN" >&2
exit 1
fi
eval "$("$FNM_BIN" env --shell bash)"
exec 9>"${FNM_DIR}.install.lock"
flock 9
"$FNM_BIN" use --install-if-missing "$node_version"
flock -u 9
if [ "$(node --version)" != "v$node_version" ]; then
echo "Node.js 切换后版本不符合要求: $(node --version)" >&2
exit 1
fi
node --version
npm --version
echo "📦 安装锁定的 Node.js 校验工具" echo "📦 安装锁定的 Node.js 校验工具"
npm ci --ignore-scripts --no-audit --no-fund --no-bin-links npm ci --ignore-scripts --no-audit --no-fund --no-bin-links
+81 -3
View File
@@ -23,6 +23,10 @@ concurrency:
env: env:
ACCESS_TOKEN: ${{ secrets.WORKFLOW }} ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
FNM_ARCHIVE_SHA256: "7807664f39d39fc518da1c35ba0181e4b3267603c4b1dedeb4b5fc6ae440a224"
FNM_BIN: "/data/bin/fnm"
FNM_DIR: "/data/fnm"
FNM_VERSION: "1.39.0"
WORKSPACE_ROOT: "/data/workspace" WORKSPACE_ROOT: "/data/workspace"
jobs: jobs:
@@ -45,10 +49,9 @@ jobs:
[curl]="curl" [curl]="curl"
[flock]="util-linux" [flock]="util-linux"
[realpath]="coreutils" [realpath]="coreutils"
[node]="nodejs" [unzip]="unzip"
[npm]="npm"
) )
required_commands=(git python3 python3.11 curl flock realpath node npm) required_commands=(git python3 python3.11 curl flock realpath unzip)
missing_commands=() missing_commands=()
missing_packages=() missing_packages=()
declare -A seen_packages=() declare -A seen_packages=()
@@ -94,6 +97,34 @@ jobs:
python3.11 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else "Python 3.11 or newer is required")' python3.11 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else "Python 3.11 or newer is required")'
fnm_installed=false
expected_fnm_version="fnm $FNM_VERSION"
current_fnm_version=""
if [ -x "$FNM_BIN" ]; then
current_fnm_version=$($FNM_BIN --version 2>/dev/null || true)
fi
if [ "$current_fnm_version" != "$expected_fnm_version" ]; then
echo "准备安装 $expected_fnm_version"
mkdir -p "$(dirname "$FNM_BIN")"
fnm_tmp_dir=$(mktemp -d)
trap 'rm -rf "$fnm_tmp_dir"' EXIT
fnm_archive="$fnm_tmp_dir/fnm-linux.zip"
curl -fsSL --retry 5 --retry-all-errors \
"https://github.com/Schniz/fnm/releases/download/v${FNM_VERSION}/fnm-linux.zip" \
-o "$fnm_archive"
printf '%s %s\n' "$FNM_ARCHIVE_SHA256" "$fnm_archive" | sha256sum -c -
unzip -q "$fnm_archive" -d "$fnm_tmp_dir"
install -m 0755 "$fnm_tmp_dir/fnm" "${FNM_BIN}.new"
mv -f "${FNM_BIN}.new" "$FNM_BIN"
fnm_installed=true
fi
if [ "$($FNM_BIN --version)" != "$expected_fnm_version" ]; then
echo "fnm 安装后版本不符合要求: $($FNM_BIN --version 2>&1)" >&2
exit 1
fi
{ {
echo "## Prepare 环境" echo "## Prepare 环境"
echo "" echo ""
@@ -105,12 +136,18 @@ jobs:
version_line=${version_line//|/\\|} version_line=${version_line//|/\\|}
printf '| `%s` | `%s` |\n' "$command" "$version_line" printf '| `%s` | `%s` |\n' "$command" "$version_line"
done done
printf '| `%s` | `%s` |\n' "fnm" "$($FNM_BIN --version)"
echo "" echo ""
if [ "$installed" = true ]; then if [ "$installed" = true ]; then
echo "安装的包:\`${missing_packages[*]}\`" echo "安装的包:\`${missing_packages[*]}\`"
else else
echo "安装的包:无,所有命令已存在。" echo "安装的包:无,所有命令已存在。"
fi fi
if [ "$fnm_installed" = true ]; then
echo "安装的版本管理器:\`$expected_fnm_version\`"
else
echo "安装的版本管理器:无,\`$expected_fnm_version\` 已存在。"
fi
} >> "$GITHUB_STEP_SUMMARY" } >> "$GITHUB_STEP_SUMMARY"
echo "installed=$installed" >> "$GITHUB_OUTPUT" echo "installed=$installed" >> "$GITHUB_OUTPUT"
@@ -205,6 +242,8 @@ jobs:
fi fi
resolved_sha=$(git --git-dir="$REPOSITORY_DIR" rev-parse "${HEAD_SHA}^{commit}") resolved_sha=$(git --git-dir="$REPOSITORY_DIR" rev-parse "${HEAD_SHA}^{commit}")
echo "PREPARED_REPOSITORY_DIR=$REPOSITORY_DIR" >> "$GITHUB_ENV"
echo "PREPARED_SHA=$resolved_sha" >> "$GITHUB_ENV"
{ {
echo "" echo ""
echo "## Prepare 仓库" echo "## Prepare 仓库"
@@ -215,3 +254,42 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY" } >> "$GITHUB_STEP_SUMMARY"
echo "Prepare 完成: $REPOSITORY_DIR @ $resolved_sha" echo "Prepare 完成: $REPOSITORY_DIR @ $resolved_sha"
- name: 🟢 准备 Node.js
shell: bash
run: |
set -euo pipefail
node_version=$(git --git-dir="$PREPARED_REPOSITORY_DIR" show "${PREPARED_SHA}:.node-version" | tr -d '[:space:]')
if ! [[ "$node_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo ".node-version 必须是完整的 Node.js 版本号: $node_version" >&2
exit 1
fi
node_major=${node_version%%.*}
if [ "$node_major" -lt 20 ]; then
echo "Node.js 版本必须不低于 20: $node_version" >&2
exit 1
fi
mkdir -p "$FNM_DIR"
exec 9>"${FNM_DIR}.install.lock"
flock 9
"$FNM_BIN" install "$node_version"
flock -u 9
actual_node_version=$($FNM_BIN exec --using="$node_version" node --version)
actual_npm_version=$($FNM_BIN exec --using="$node_version" npm --version)
if [ "$actual_node_version" != "v$node_version" ]; then
echo "Node.js 安装后版本不符合要求: $actual_node_version" >&2
exit 1
fi
{
echo ""
echo "## Prepare Node.js"
echo ""
echo "- fnm\`$($FNM_BIN --version)\`"
echo "- Node.js\`$actual_node_version\`"
echo "- npm\`$actual_npm_version\`"
echo "- 缓存目录:\`$FNM_DIR\`"
} >> "$GITHUB_STEP_SUMMARY"
+1
View File
@@ -0,0 +1 @@
24.19.0
+3
View File
@@ -5,6 +5,9 @@
"packages": { "packages": {
"": { "": {
"name": "playbook", "name": "playbook",
"engines": {
"node": ">=20"
},
"devDependencies": { "devDependencies": {
"markdownlint-cli": "0.48.0", "markdownlint-cli": "0.48.0",
"prettier": "3.9.6" "prettier": "3.9.6"
+3
View File
@@ -1,6 +1,9 @@
{ {
"name": "playbook", "name": "playbook",
"private": true, "private": true,
"engines": {
"node": ">=20"
},
"devDependencies": { "devDependencies": {
"markdownlint-cli": "0.48.0", "markdownlint-cli": "0.48.0",
"prettier": "3.9.6" "prettier": "3.9.6"
+13 -2
View File
@@ -13,6 +13,7 @@ LOOKUP_PATH = SKILL_ROOT / "scripts" / "lookup.py"
SKILL_PATH = SKILL_ROOT / "SKILL.md" SKILL_PATH = SKILL_ROOT / "SKILL.md"
CI_PATH = ROOT / ".gitea" / "workflows" / "checks.yml" CI_PATH = ROOT / ".gitea" / "workflows" / "checks.yml"
PREPARE_PATH = ROOT / ".gitea" / "workflows" / "prepare.yml" PREPARE_PATH = ROOT / ".gitea" / "workflows" / "prepare.yml"
NODE_VERSION_PATH = ROOT / ".node-version"
def load_lookup_module(): def load_lookup_module():
@@ -445,8 +446,18 @@ class TslSyntaxReferenceTests(unittest.TestCase):
"node_modules/prettier/bin/prettier.cjs --check skills/tsl-syntax-reference", "node_modules/prettier/bin/prettier.cjs --check skills/tsl-syntax-reference",
workflow, workflow,
) )
self.assertIn("[node]=\"nodejs\"", prepare) self.assertIn("[unzip]=\"unzip\"", prepare)
self.assertIn("[npm]=\"npm\"", prepare) self.assertIn('FNM_BIN: "/data/bin/fnm"', prepare)
self.assertIn('FNM_DIR: "/data/fnm"', prepare)
self.assertIn('"$FNM_BIN" install "$node_version"', prepare)
self.assertNotIn('[node]="nodejs"', prepare)
self.assertNotIn('[npm]="npm"', prepare)
self.assertIn('"$FNM_BIN" use --install-if-missing "$node_version"', workflow)
self.assertIn("node --version", workflow)
self.assertRegex(
NODE_VERSION_PATH.read_text(encoding="utf-8").strip(),
r"^\d+\.\d+\.\d+$",
)
if __name__ == "__main__": if __name__ == "__main__":