-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrelease-check.sh
More file actions
executable file
·118 lines (105 loc) · 4.27 KB
/
Copy pathrelease-check.sh
File metadata and controls
executable file
·118 lines (105 loc) · 4.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/bin/sh
# 发版前自检:把踩过的坑都变成会失败的检查。
#
# ./release-check.sh
#
# 插件源码与 marketplace 同仓(source 是相对路径,不钉 sha),所以检查全部在本地
# 完成,不需要网络。以前那三项与 sha 有关的检查——40 位完整 sha、sha 是上游分支的
# 祖先、path 在该 sha 上存在——随着 sha 一起作废了。
#
# 1. marketplace.json 能解析,且 claude plugin validate 通过
# 2. marketplace 条目的 version 与 plugin.json 的 version 一致 —— 装的时候以
# plugin.json 为准,两处不一致时 marketplace 标了新版也没用
# 3. source 指向的目录存在,且带 .claude-plugin/plugin.json
# 4. bin/ 下的程序有可执行位 —— 丢了 x 位,命令进了 PATH 也跑不起来
# 5. lib/ 与 bin/ 的 Python 能编译
#
# 只用 sh + python3(标准库)。
set -eu
DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
MANIFEST="$DIR/.claude-plugin/marketplace.json"
[ -f "$MANIFEST" ] || { echo "找不到 $MANIFEST" >&2; exit 1; }
if command -v claude >/dev/null 2>&1; then
echo "▸ claude plugin validate"
claude plugin validate "$DIR" >/dev/null || { echo " ✘ validate 未通过,跑 claude plugin validate . 看详情" >&2; exit 1; }
echo " ✔ 通过"
else
echo "▸ claude 不在 PATH,跳过 validate(其余检查照常)"
fi
python3 - "$MANIFEST" "$DIR" <<'PY'
import json
import os
import py_compile
import stat
import sys
import tempfile
manifest_path, root = sys.argv[1], sys.argv[2]
manifest = json.load(open(manifest_path, encoding='utf-8'))
fails = []
for entry in manifest.get('plugins', []):
name = entry.get('name', '?')
version = entry.get('version')
src = entry.get('source')
print(f"\n▸ {name} {version}")
if not isinstance(src, str):
fails.append(f'{name}: source 不是相对路径字符串(当前 {src!r})。'
'同仓插件应写成 "./plugins/<名字>"')
continue
plugin_dir = os.path.normpath(os.path.join(root, src))
if not os.path.isdir(plugin_dir):
fails.append(f'{name}: source 指向的目录不存在:{src}')
continue
print(f' ✔ 目录存在 {src}')
# 2 + 3:plugin.json 必须在,且版本要对得上
plugin_json = os.path.join(plugin_dir, '.claude-plugin', 'plugin.json')
if not os.path.isfile(plugin_json):
fails.append(f'{name}: 缺 .claude-plugin/plugin.json')
continue
declared = json.load(open(plugin_json, encoding='utf-8')).get('version')
if declared != version:
fails.append(f'{name}: marketplace 写 {version},plugin.json 写 {declared}。'
'装的时候以 plugin.json 为准,两者必须一致')
else:
print(f' ✔ 版本一致 {declared}')
# 4:可执行位
bin_dir = os.path.join(plugin_dir, 'bin')
if os.path.isdir(bin_dir):
for f in sorted(os.listdir(bin_dir)):
path = os.path.join(bin_dir, f)
if not os.path.isfile(path):
continue
if not os.stat(path).st_mode & stat.S_IXUSR:
fails.append(f'{name}: bin/{f} 没有可执行位,chmod +x')
else:
print(f' ✔ bin/{f} 可执行')
# 5:Python 能编译
bad = []
for sub in ('lib', 'bin'):
d = os.path.join(plugin_dir, sub)
if not os.path.isdir(d):
continue
for f in sorted(os.listdir(d)):
path = os.path.join(d, f)
if not os.path.isfile(path):
continue
if sub == 'lib' and not f.endswith('.py'):
continue
with open(path, 'rb') as fh:
if sub == 'bin' and b'python' not in fh.readline():
continue
try:
with tempfile.NamedTemporaryFile(suffix='.pyc', delete=True) as out:
py_compile.compile(path, cfile=out.name, doraise=True)
except py_compile.PyCompileError as e:
bad.append(f'{sub}/{f}: {e.msg.strip().splitlines()[-1]}')
if bad:
fails.extend(f'{name}: {b}' for b in bad)
else:
print(' ✔ Python 全部可编译')
print()
if fails:
for f in fails:
print(f'✘ {f}')
sys.exit(1)
print('全部通过,可以推。')
PY