99from pathlib import Path
1010
1111
12+ MIN_NODE_VERSION_FOR_NPM_OIDC = (22 , 14 , 0 )
13+ MIN_NPM_VERSION_FOR_OIDC = (11 , 5 , 1 )
14+
15+
1216def run_command (cmd : list [str ], cwd : Path | None = None , shell : bool = False ) -> int :
1317 """执行命令并实时输出"""
1418 print (f"\n [执行] { ' ' .join (cmd )} " )
@@ -34,6 +38,77 @@ def is_github_actions() -> bool:
3438 return os .getenv ('GITHUB_ACTIONS' ) == 'true'
3539
3640
41+ def command_output (cmd : list [str ], cwd : Path | None = None ) -> str :
42+ """执行命令并返回 stdout"""
43+ result = subprocess .run (cmd , cwd = cwd , text = True , stdout = subprocess .PIPE , stderr = subprocess .STDOUT )
44+ if result .returncode != 0 :
45+ raise RuntimeError (result .stdout .strip () or f"command failed: { ' ' .join (cmd )} " )
46+ return result .stdout .strip ()
47+
48+
49+ def parse_version (value : str ) -> tuple [int , int , int ]:
50+ """解析 node/npm 版本号,忽略 v 前缀和预发布后缀"""
51+ cleaned = value .strip ().lstrip ('v' ).split ('-' , 1 )[0 ]
52+ parts = cleaned .split ('.' )
53+ version = []
54+ for part in parts [:3 ]:
55+ try :
56+ version .append (int (part ))
57+ except ValueError :
58+ version .append (0 )
59+ while len (version ) < 3 :
60+ version .append (0 )
61+ return tuple (version )
62+
63+
64+ def check_github_actions_oidc (root_dir : Path ) -> int :
65+ """校验 GitHub Actions OIDC 发布所需环境"""
66+ print ("\n [认证] GitHub Actions 环境:使用 npm Trusted Publishing (OIDC)" )
67+
68+ request_token = os .getenv ('ACTIONS_ID_TOKEN_REQUEST_TOKEN' )
69+ request_url = os .getenv ('ACTIONS_ID_TOKEN_REQUEST_URL' )
70+ if not request_token or not request_url :
71+ print ("[错误] 未检测到 GitHub Actions OIDC 请求环境变量" )
72+ print ("[提示] 请确认 workflow job 配置了 permissions.id-token: write,并使用 GitHub-hosted runner" )
73+ return 1
74+
75+ try :
76+ node_version_text = command_output (["node" , "--version" ], cwd = root_dir )
77+ npm_version_text = command_output (["npm" , "--version" ], cwd = root_dir )
78+ except RuntimeError as exc :
79+ print (f"[错误] 检查 node/npm 版本失败: { exc } " )
80+ return 1
81+
82+ node_version = parse_version (node_version_text )
83+ npm_version = parse_version (npm_version_text )
84+ print (f"[认证] Node.js: { node_version_text } " )
85+ print (f"[认证] npm: { npm_version_text } " )
86+
87+ if node_version < MIN_NODE_VERSION_FOR_NPM_OIDC :
88+ required = "." .join (str (part ) for part in MIN_NODE_VERSION_FOR_NPM_OIDC )
89+ print (f"[错误] npm OIDC 发布需要 Node.js >= { required } " )
90+ return 1
91+
92+ if npm_version < MIN_NPM_VERSION_FOR_OIDC :
93+ required = "." .join (str (part ) for part in MIN_NPM_VERSION_FOR_OIDC )
94+ print (f"[错误] npm OIDC 发布需要 npm >= { required } " )
95+ return 1
96+
97+ print ("[认证] OIDC 环境检查通过,npm publish 将在发布时换取短期令牌" )
98+ return 0
99+
100+
101+ def check_local_npm_auth (root_dir : Path ) -> int :
102+ """本地发布仍使用当前 npm 登录状态"""
103+ print ("\n [认证] 本地环境:检查 npm 登录状态..." )
104+ auth_result = run_command (["npm" , "whoami" ], cwd = root_dir )
105+ if auth_result != 0 :
106+ print ("\n [错误] npm whoami 失败,请先运行 npm login 或改用 GitHub Actions OIDC 发布" )
107+ return auth_result
108+ print ("[认证] npm 登录状态正常" )
109+ return 0
110+
111+
37112def main ():
38113 root_dir = Path (__file__ ).parent .absolute ()
39114 npm_packages_dir = root_dir / "npm-packages"
@@ -66,42 +141,19 @@ def main():
66141 "linux-arm64"
67142 ]
68143
69- # 调试信息:检查 npm 认证
70- print ("\n [调试] 检查 npm 认证状态..." )
71- check_auth_cmd = ["npm" , "whoami" ]
72- auth_result = run_command (check_auth_cmd , cwd = root_dir )
73-
74- if auth_result != 0 :
75- print ("\n [警告] npm whoami 失败,可能未正确认证" )
76- print ("[调试] 检查 .npmrc 配置..." )
77- npmrc_path = Path .home () / ".npmrc"
78- if npmrc_path .exists ():
79- print (f"[调试] 找到 .npmrc: { npmrc_path } " )
80- # 不打印内容,避免泄露 token
81- else :
82- print (f"[警告] 未找到 .npmrc: { npmrc_path } " )
83-
84- if is_github_actions ():
85- print ("[调试] 在 GitHub Actions 中,检查 NODE_AUTH_TOKEN 环境变量..." )
86- if os .getenv ('NODE_AUTH_TOKEN' ):
87- print ("[调试] NODE_AUTH_TOKEN 已设置 (长度: {})" .format (len (os .getenv ('NODE_AUTH_TOKEN' , '' ))))
88- else :
89- print ("[错误] NODE_AUTH_TOKEN 未设置!" )
90- return 1
144+ if is_github_actions ():
145+ auth_result = check_github_actions_oidc (root_dir )
91146 else :
92- print ("[调试] npm 认证成功" )
147+ auth_result = check_local_npm_auth (root_dir )
148+ if auth_result != 0 :
149+ return auth_result
93150
94151 # 构建发布命令
95152 publish_cmd = ["npm" , "publish" , "--access" , "public" ]
96-
97- # Granular access tokens 在某些情况下与 --provenance 不兼容
98- # 暂时禁用以测试基本发布功能
99- use_provenance = False
100- if is_github_actions () and use_provenance :
101- publish_cmd .append ("--provenance" )
102- print ("\n [信息] 检测到 GitHub Actions 环境,将使用 --provenance 发布" )
153+ if is_github_actions ():
154+ print ("\n [信息] 使用 npm Trusted Publishing (OIDC) 发布" )
103155 else :
104- print ("\n [信息] 使用标准发布模式(不使用 provenance) " )
156+ print ("\n [信息] 使用本地 npm 登录凭据发布 " )
105157
106158 # 发布所有平台包
107159 print ("\n [步骤 1/2] 发布平台包" )
0 commit comments