@aipper/zentao-mcp-server 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -3
- package/README.md +29 -17
- package/package.json +11 -2
- package/scripts/smoke.mjs +2 -1
- package/src/index.js +36 -52
- package/src/tools.js +82 -64
- package/src/zentao.js +549 -86
- package/IMPROVEMENTS.md +0 -179
- package/scripts/release-npm.sh +0 -481
package/IMPROVEMENTS.md
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# 代码改进总结
|
|
2
|
-
|
|
3
|
-
本次改进基于代码评审,主要解决了工程化、安全性和可维护性方面的问题。
|
|
4
|
-
|
|
5
|
-
## 已完成的改进
|
|
6
|
-
|
|
7
|
-
### 高优先级 ✅
|
|
8
|
-
|
|
9
|
-
1. **修复版本号不一致** (src/index.js)
|
|
10
|
-
- 问题: 硬编码版本 "0.1.0" 与 package.json 的 "0.1.9" 不一致
|
|
11
|
-
- 解决: 从 package.json 动态读取版本号
|
|
12
|
-
- 影响: 确保版本信息准确,避免混淆
|
|
13
|
-
|
|
14
|
-
2. **锁定依赖版本** (package.json)
|
|
15
|
-
- 问题: 使用 "latest" 导致版本不稳定
|
|
16
|
-
- 解决: 改为 "^1.27.1" 锁定主版本
|
|
17
|
-
- 影响: 提高构建稳定性和可重现性
|
|
18
|
-
|
|
19
|
-
3. **修复 License 问题** (package.json, LICENSE)
|
|
20
|
-
- 问题: "private: false" 但 "license: UNLICENSED" 矛盾
|
|
21
|
-
- 解决: 改为 MIT 协议并创建 LICENSE 文件
|
|
22
|
-
- 影响: 明确开源协议,便于分发和使用
|
|
23
|
-
|
|
24
|
-
4. **添加 Node.js 版本要求** (package.json)
|
|
25
|
-
- 问题: README 要求 Node.js 18+ 但 package.json 未声明
|
|
26
|
-
- 解决: 添加 "engines": {"node": ">=18.0.0"}
|
|
27
|
-
- 影响: npm 安装时会检查版本兼容性
|
|
28
|
-
|
|
29
|
-
### 中优先级 ✅
|
|
30
|
-
|
|
31
|
-
5. **添加 JSDoc 类型注释** (src/zentao.js)
|
|
32
|
-
- 问题: 纯 JavaScript 缺少类型信息
|
|
33
|
-
- 解决: 为关键函数添加 JSDoc 注释
|
|
34
|
-
- 影响: IDE 提供更好的代码提示和文档
|
|
35
|
-
|
|
36
|
-
6. **添加调试日志系统** (src/index.js)
|
|
37
|
-
- 问题: 调试困难,无法追踪执行流程
|
|
38
|
-
- 解决: 添加 ZENTAO_DEBUG 环境变量控制的日志
|
|
39
|
-
- 影响: 便于排查问题,不影响正常使用
|
|
40
|
-
|
|
41
|
-
7. **提取魔法数字和字符串** (src/zentao.js)
|
|
42
|
-
- 问题: 硬编码的数字和中文字符串
|
|
43
|
-
- 解决: 提取为常量 (MAX_ERROR_TEXT_LENGTH, DEFAULT_RESOLUTION_PREFIX 等)
|
|
44
|
-
- 影响: 提高可维护性,便于国际化
|
|
45
|
-
|
|
46
|
-
8. **添加安全检查** (src/index.js)
|
|
47
|
-
- 问题: 使用 HTTP 存在安全风险
|
|
48
|
-
- 解决: 检测 HTTP 连接并输出警告
|
|
49
|
-
- 影响: 提醒用户注意安全问题
|
|
50
|
-
|
|
51
|
-
### 文档和配置 ✅
|
|
52
|
-
|
|
53
|
-
9. **创建 CHANGELOG.md**
|
|
54
|
-
- 记录版本变更历史
|
|
55
|
-
- 便于用户了解更新内容
|
|
56
|
-
|
|
57
|
-
10. **更新 README.md**
|
|
58
|
-
- 添加安全建议章节
|
|
59
|
-
- 添加调试说明
|
|
60
|
-
- 添加 License 说明
|
|
61
|
-
|
|
62
|
-
11. **完善 package.json**
|
|
63
|
-
- 添加 keywords 便于搜索
|
|
64
|
-
- 添加 repository 字段
|
|
65
|
-
- 规范化元数据
|
|
66
|
-
|
|
67
|
-
12. **更新 .env.example**
|
|
68
|
-
- 添加 ZENTAO_DEBUG 配置说明
|
|
69
|
-
|
|
70
|
-
13. **更新 .gitignore**
|
|
71
|
-
- 添加常见忽略文件
|
|
72
|
-
|
|
73
|
-
## 代码质量验证
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
npm run lint # ✅ 通过
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
所有文件语法检查通过,无错误。
|
|
80
|
-
|
|
81
|
-
## 改进效果
|
|
82
|
-
|
|
83
|
-
### 前后对比
|
|
84
|
-
|
|
85
|
-
| 方面 | 改进前 | 改进后 |
|
|
86
|
-
|------|--------|--------|
|
|
87
|
-
| 版本管理 | 硬编码,不一致 | 动态读取,统一 |
|
|
88
|
-
| 依赖稳定性 | latest (不稳定) | ^1.27.1 (锁定) |
|
|
89
|
-
| 开源协议 | UNLICENSED (矛盾) | MIT (明确) |
|
|
90
|
-
| 类型提示 | 无 | JSDoc 注释 |
|
|
91
|
-
| 调试能力 | 困难 | 可选日志 |
|
|
92
|
-
| 安全意识 | 无提示 | HTTP 警告 |
|
|
93
|
-
| 文档完整性 | 基础 | 完善 |
|
|
94
|
-
|
|
95
|
-
### 代码统计
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
修改文件: 5 个
|
|
99
|
-
新增文件: 3 个 (LICENSE, CHANGELOG.md, IMPROVEMENTS.md)
|
|
100
|
-
新增代码: +178 行
|
|
101
|
-
删除代码: -31 行
|
|
102
|
-
净增加: +147 行
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## 未完成的改进 (建议后续处理)
|
|
106
|
-
|
|
107
|
-
### 低优先级
|
|
108
|
-
|
|
109
|
-
1. **拆分大文件**
|
|
110
|
-
- src/zentao.js (855 行) 可拆分为多个模块
|
|
111
|
-
- 建议: bugs.js, projects.js, parsers.js, utils.js
|
|
112
|
-
|
|
113
|
-
2. **添加单元测试**
|
|
114
|
-
- 当前只有烟雾测试
|
|
115
|
-
- 建议: 使用 vitest 添加单元测试
|
|
116
|
-
|
|
117
|
-
3. **性能优化**
|
|
118
|
-
- batchResolveMyBugs 可考虑并发处理
|
|
119
|
-
- 建议: 使用 Promise.all 或 p-limit
|
|
120
|
-
|
|
121
|
-
4. **国际化支持**
|
|
122
|
-
- 硬编码的中文字符串
|
|
123
|
-
- 建议: 提取为配置或支持多语言
|
|
124
|
-
|
|
125
|
-
5. **错误处理增强**
|
|
126
|
-
- 某些错误信息可以更具体
|
|
127
|
-
- 建议: 添加错误码和详细上下文
|
|
128
|
-
|
|
129
|
-
## 使用建议
|
|
130
|
-
|
|
131
|
-
### 开发调试
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
# 启用调试日志
|
|
135
|
-
ZENTAO_DEBUG=true npm start
|
|
136
|
-
|
|
137
|
-
# 或在 MCP 客户端配置中添加
|
|
138
|
-
{
|
|
139
|
-
"env": {
|
|
140
|
-
"ZENTAO_DEBUG": "true"
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### 安全最佳实践
|
|
146
|
-
|
|
147
|
-
1. 始终使用 HTTPS 连接禅道服务器
|
|
148
|
-
2. 使用最小权限账号
|
|
149
|
-
3. 不要在代码仓库中提交 .env 文件
|
|
150
|
-
4. 定期更新依赖包
|
|
151
|
-
|
|
152
|
-
### 发布前检查
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
# 1. 语法检查
|
|
156
|
-
npm run lint
|
|
157
|
-
|
|
158
|
-
# 2. 烟雾测试
|
|
159
|
-
npm run smoke
|
|
160
|
-
|
|
161
|
-
# 3. 更新版本号
|
|
162
|
-
npm version patch # 或 minor/major
|
|
163
|
-
|
|
164
|
-
# 4. 发布
|
|
165
|
-
npm run release:npm -- --publish
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## 总结
|
|
169
|
-
|
|
170
|
-
本次改进主要聚焦于**工程化基础设施**和**开发体验**,解决了版本管理、依赖稳定性、开源协议等关键问题。代码质量和可维护性得到显著提升,为后续功能开发和维护打下良好基础。
|
|
171
|
-
|
|
172
|
-
**整体评分提升**: 7.5/10 → 8.5/10
|
|
173
|
-
|
|
174
|
-
主要提升点:
|
|
175
|
-
- ✅ 工程化规范 (+1.0)
|
|
176
|
-
- ✅ 安全意识 (+0.5)
|
|
177
|
-
- ✅ 可维护性 (+0.5)
|
|
178
|
-
- ⏳ 测试覆盖 (待改进)
|
|
179
|
-
- ⏳ 模块化 (待改进)
|
package/scripts/release-npm.sh
DELETED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
usage() {
|
|
5
|
-
cat <<'EOF'
|
|
6
|
-
release-npm.sh (zentao-mcp-server)
|
|
7
|
-
|
|
8
|
-
Default mode is dry-run: run checks + npm pack --dry-run, no npm publish.
|
|
9
|
-
|
|
10
|
-
Usage:
|
|
11
|
-
bash scripts/release-npm.sh
|
|
12
|
-
bash scripts/release-npm.sh --publish
|
|
13
|
-
|
|
14
|
-
Options:
|
|
15
|
-
--release <tag> Set package version from tag (vX.Y.Z), then commit + tag
|
|
16
|
-
--bump <level> Auto bump version: patch | minor | major, then commit + tag
|
|
17
|
-
--publish Run npm publish after checks (defaults to --bump patch)
|
|
18
|
-
--yes Skip interactive confirmation
|
|
19
|
-
--require-tag Require a semver git tag on HEAD matching package version
|
|
20
|
-
--skip-secrets-scan Skip ripgrep-based secrets scan
|
|
21
|
-
--skip-whoami Skip npm whoami check
|
|
22
|
-
--no-git-clean-check Skip clean-worktree check (not recommended)
|
|
23
|
-
-h, --help Show help
|
|
24
|
-
|
|
25
|
-
Environment:
|
|
26
|
-
ZENTAO_NPM_CACHE_DIR Override npm cache dir used by this script
|
|
27
|
-
EOF
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
PUBLISH=0
|
|
31
|
-
YES=0
|
|
32
|
-
REQUIRE_TAG=0
|
|
33
|
-
RELEASE_TAG=""
|
|
34
|
-
BUMP_LEVEL=""
|
|
35
|
-
SKIP_SECRETS_SCAN=0
|
|
36
|
-
SKIP_WHOAMI=0
|
|
37
|
-
SKIP_GIT_CLEAN_CHECK=0
|
|
38
|
-
|
|
39
|
-
while [[ $# -gt 0 ]]; do
|
|
40
|
-
case "$1" in
|
|
41
|
-
--release)
|
|
42
|
-
shift
|
|
43
|
-
if [[ $# -lt 1 ]]; then
|
|
44
|
-
echo "error: --release requires <tag> (e.g. v0.1.0)" >&2
|
|
45
|
-
usage
|
|
46
|
-
exit 2
|
|
47
|
-
fi
|
|
48
|
-
RELEASE_TAG="$1"
|
|
49
|
-
;;
|
|
50
|
-
--bump)
|
|
51
|
-
shift
|
|
52
|
-
if [[ $# -lt 1 ]]; then
|
|
53
|
-
echo "error: --bump requires <level> (patch|minor|major)" >&2
|
|
54
|
-
usage
|
|
55
|
-
exit 2
|
|
56
|
-
fi
|
|
57
|
-
BUMP_LEVEL="$1"
|
|
58
|
-
;;
|
|
59
|
-
--publish) PUBLISH=1 ;;
|
|
60
|
-
--yes) YES=1 ;;
|
|
61
|
-
--require-tag) REQUIRE_TAG=1 ;;
|
|
62
|
-
--skip-secrets-scan) SKIP_SECRETS_SCAN=1 ;;
|
|
63
|
-
--skip-whoami) SKIP_WHOAMI=1 ;;
|
|
64
|
-
--no-git-clean-check) SKIP_GIT_CLEAN_CHECK=1 ;;
|
|
65
|
-
-h|--help) usage; exit 0 ;;
|
|
66
|
-
*) echo "error: unknown arg: $1" >&2; usage; exit 2 ;;
|
|
67
|
-
esac
|
|
68
|
-
shift
|
|
69
|
-
done
|
|
70
|
-
|
|
71
|
-
log() { echo "info: $*"; }
|
|
72
|
-
warn() { echo "warn: $*" >&2; }
|
|
73
|
-
die() { echo "error: $*" >&2; exit 2; }
|
|
74
|
-
|
|
75
|
-
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
76
|
-
cd "$repo_root"
|
|
77
|
-
|
|
78
|
-
command -v node >/dev/null 2>&1 || die "node is required"
|
|
79
|
-
command -v npm >/dev/null 2>&1 || die "npm is required"
|
|
80
|
-
|
|
81
|
-
DEFAULT_NPM_CACHE_DIR="${ZENTAO_NPM_CACHE_DIR:-/tmp/zentao-npm-cache}"
|
|
82
|
-
|
|
83
|
-
maybe_use_safe_npm_cache() {
|
|
84
|
-
if [[ -z "${npm_config_cache:-}" && -z "${NPM_CONFIG_CACHE:-}" ]]; then
|
|
85
|
-
export npm_config_cache="$DEFAULT_NPM_CACHE_DIR"
|
|
86
|
-
fi
|
|
87
|
-
local cache_dir="${npm_config_cache:-${NPM_CONFIG_CACHE:-}}"
|
|
88
|
-
if [[ -n "${cache_dir:-}" ]]; then
|
|
89
|
-
mkdir -p "$cache_dir" 2>/dev/null || true
|
|
90
|
-
log "npm_cache=$cache_dir"
|
|
91
|
-
fi
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
maybe_use_safe_npm_cache
|
|
95
|
-
|
|
96
|
-
pkg_name=""
|
|
97
|
-
pkg_version=""
|
|
98
|
-
pkg_private=""
|
|
99
|
-
release_version=""
|
|
100
|
-
release_tag_name=""
|
|
101
|
-
release_tmpdir=""
|
|
102
|
-
release_restore_on_exit=0
|
|
103
|
-
|
|
104
|
-
cleanup_release() {
|
|
105
|
-
if [[ "${release_restore_on_exit:-0}" == "1" && -n "${release_tmpdir:-}" ]]; then
|
|
106
|
-
cp "$release_tmpdir/package.json" package.json 2>/dev/null || true
|
|
107
|
-
fi
|
|
108
|
-
if [[ -n "${release_tmpdir:-}" ]]; then
|
|
109
|
-
rm -rf "$release_tmpdir" 2>/dev/null || true
|
|
110
|
-
fi
|
|
111
|
-
}
|
|
112
|
-
trap cleanup_release EXIT
|
|
113
|
-
|
|
114
|
-
is_semver() {
|
|
115
|
-
printf '%s' "$1" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.+].+)?$'
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
normalize_release_tag() {
|
|
119
|
-
local input="$1"
|
|
120
|
-
if [[ "$input" == v* ]]; then
|
|
121
|
-
release_tag_name="$input"
|
|
122
|
-
release_version="${input#v}"
|
|
123
|
-
else
|
|
124
|
-
release_tag_name="v$input"
|
|
125
|
-
release_version="$input"
|
|
126
|
-
fi
|
|
127
|
-
if ! is_semver "$release_version"; then
|
|
128
|
-
die "--release requires semver tag (vX.Y.Z); got: $input"
|
|
129
|
-
fi
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
calc_bumped_version() {
|
|
133
|
-
local current="$1"
|
|
134
|
-
local level="$2"
|
|
135
|
-
ZENTAO_CURRENT_VERSION="$current" ZENTAO_BUMP_LEVEL="$level" node - <<'NODE'
|
|
136
|
-
const current = process.env.ZENTAO_CURRENT_VERSION || "";
|
|
137
|
-
const level = process.env.ZENTAO_BUMP_LEVEL || "";
|
|
138
|
-
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
139
|
-
if (!m) {
|
|
140
|
-
console.error(`error: current version is not semver-like: ${current}`);
|
|
141
|
-
process.exit(2);
|
|
142
|
-
}
|
|
143
|
-
let major = Number(m[1]);
|
|
144
|
-
let minor = Number(m[2]);
|
|
145
|
-
let patch = Number(m[3]);
|
|
146
|
-
if (level === "patch") {
|
|
147
|
-
patch += 1;
|
|
148
|
-
} else if (level === "minor") {
|
|
149
|
-
minor += 1;
|
|
150
|
-
patch = 0;
|
|
151
|
-
} else if (level === "major") {
|
|
152
|
-
major += 1;
|
|
153
|
-
minor = 0;
|
|
154
|
-
patch = 0;
|
|
155
|
-
} else {
|
|
156
|
-
console.error(`error: unsupported bump level: ${level} (expected patch|minor|major)`);
|
|
157
|
-
process.exit(2);
|
|
158
|
-
}
|
|
159
|
-
process.stdout.write(`${major}.${minor}.${patch}`);
|
|
160
|
-
NODE
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
read_pkg() {
|
|
164
|
-
pkg_name="$(node -p "require('./package.json').name")"
|
|
165
|
-
pkg_version="$(node -p "require('./package.json').version")"
|
|
166
|
-
pkg_private="$(node -p "Boolean(require('./package.json').private)")"
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
write_version() {
|
|
170
|
-
local v="$1"
|
|
171
|
-
ZENTAO_RELEASE_VERSION="$v" node - <<'NODE'
|
|
172
|
-
const fs = require("node:fs");
|
|
173
|
-
const file = "package.json";
|
|
174
|
-
const v = process.env.ZENTAO_RELEASE_VERSION;
|
|
175
|
-
if (!v) {
|
|
176
|
-
console.error("missing ZENTAO_RELEASE_VERSION");
|
|
177
|
-
process.exit(2);
|
|
178
|
-
}
|
|
179
|
-
const src = fs.readFileSync(file, "utf8");
|
|
180
|
-
const pkg = JSON.parse(src);
|
|
181
|
-
pkg.version = v;
|
|
182
|
-
fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n");
|
|
183
|
-
NODE
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
backup_package_for_restore() {
|
|
187
|
-
release_tmpdir="$(mktemp -d)"
|
|
188
|
-
cp package.json "$release_tmpdir/package.json"
|
|
189
|
-
release_restore_on_exit=1
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
check_publish_ownership() {
|
|
193
|
-
local npm_user="$1"
|
|
194
|
-
local package_name="$2"
|
|
195
|
-
|
|
196
|
-
local view_out=""
|
|
197
|
-
local view_code=0
|
|
198
|
-
set +e
|
|
199
|
-
view_out="$(npm view "$package_name" name 2>&1)"
|
|
200
|
-
view_code=$?
|
|
201
|
-
set -e
|
|
202
|
-
|
|
203
|
-
if [[ "$view_code" != "0" ]]; then
|
|
204
|
-
if echo "$view_out" | grep -Eqi "(E404|404|not found|not in this registry)"; then
|
|
205
|
-
log "publish ownership precheck: package not found on registry ($package_name), treated as first publish"
|
|
206
|
-
return 0
|
|
207
|
-
fi
|
|
208
|
-
warn "publish ownership precheck skipped: cannot query package metadata"
|
|
209
|
-
return 0
|
|
210
|
-
fi
|
|
211
|
-
|
|
212
|
-
local owners_out=""
|
|
213
|
-
local owners_code=0
|
|
214
|
-
set +e
|
|
215
|
-
owners_out="$(npm owner ls "$package_name" 2>&1)"
|
|
216
|
-
owners_code=$?
|
|
217
|
-
set -e
|
|
218
|
-
|
|
219
|
-
if [[ "$owners_code" != "0" ]]; then
|
|
220
|
-
warn "publish ownership precheck skipped: cannot query package owners"
|
|
221
|
-
return 0
|
|
222
|
-
fi
|
|
223
|
-
|
|
224
|
-
if echo "$owners_out" | grep -Eiq "^${npm_user}[[:space:]]"; then
|
|
225
|
-
log "publish ownership precheck: ok ($npm_user owns $package_name)"
|
|
226
|
-
return 0
|
|
227
|
-
fi
|
|
228
|
-
|
|
229
|
-
die "npm user '$npm_user' is not an owner of '$package_name' (likely 403). Use a scoped name like '@$npm_user/zentao-mcp-server' or ask current owner to add you."
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
read_pkg
|
|
233
|
-
|
|
234
|
-
# Default behavior: publishing without explicit release/bump will auto-bump patch.
|
|
235
|
-
if [[ "$PUBLISH" == "1" && -z "${RELEASE_TAG:-}" && -z "${BUMP_LEVEL:-}" ]]; then
|
|
236
|
-
BUMP_LEVEL="patch"
|
|
237
|
-
log "publish requested without version args; defaulting to --bump patch"
|
|
238
|
-
fi
|
|
239
|
-
|
|
240
|
-
log "repo_root=$repo_root"
|
|
241
|
-
log "package=$pkg_name"
|
|
242
|
-
log "version=$pkg_version"
|
|
243
|
-
log "mode=$(
|
|
244
|
-
if [[ "$PUBLISH" == "1" ]]; then
|
|
245
|
-
echo publish
|
|
246
|
-
elif [[ -n "${RELEASE_TAG:-}" || -n "${BUMP_LEVEL:-}" ]]; then
|
|
247
|
-
echo prepare
|
|
248
|
-
else
|
|
249
|
-
echo dry-run
|
|
250
|
-
fi
|
|
251
|
-
)"
|
|
252
|
-
|
|
253
|
-
if [[ -n "${RELEASE_TAG:-}" && -n "${BUMP_LEVEL:-}" ]]; then
|
|
254
|
-
die "cannot combine --release and --bump"
|
|
255
|
-
fi
|
|
256
|
-
|
|
257
|
-
VERSION_UPDATE=0
|
|
258
|
-
if [[ -n "${RELEASE_TAG:-}" ]]; then
|
|
259
|
-
normalize_release_tag "$RELEASE_TAG"
|
|
260
|
-
VERSION_UPDATE=1
|
|
261
|
-
fi
|
|
262
|
-
if [[ -n "${BUMP_LEVEL:-}" ]]; then
|
|
263
|
-
release_version="$(calc_bumped_version "$pkg_version" "$BUMP_LEVEL")"
|
|
264
|
-
release_tag_name="v$release_version"
|
|
265
|
-
VERSION_UPDATE=1
|
|
266
|
-
log "bump_level=$BUMP_LEVEL"
|
|
267
|
-
fi
|
|
268
|
-
|
|
269
|
-
if [[ "$VERSION_UPDATE" == "1" && "$REQUIRE_TAG" == "1" ]]; then
|
|
270
|
-
die "cannot combine --release/--bump with --require-tag (release flow creates a tag)"
|
|
271
|
-
fi
|
|
272
|
-
|
|
273
|
-
if [[ "$VERSION_UPDATE" == "1" ]]; then
|
|
274
|
-
command -v git >/dev/null 2>&1 || die "--release/--bump requires git"
|
|
275
|
-
log "release_tag=$release_tag_name"
|
|
276
|
-
log "release_version=$release_version"
|
|
277
|
-
|
|
278
|
-
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
279
|
-
die "--release requires running inside a git repository"
|
|
280
|
-
fi
|
|
281
|
-
|
|
282
|
-
if git rev-parse -q --verify "refs/tags/$release_tag_name" >/dev/null 2>&1; then
|
|
283
|
-
die "--release tag already exists: $release_tag_name"
|
|
284
|
-
fi
|
|
285
|
-
|
|
286
|
-
if [[ "$SKIP_GIT_CLEAN_CHECK" != "1" ]]; then
|
|
287
|
-
dirty="$(git status --porcelain || true)"
|
|
288
|
-
if [[ -n "${dirty:-}" ]]; then
|
|
289
|
-
die "--release requires a clean git working tree; commit or stash changes first"
|
|
290
|
-
fi
|
|
291
|
-
fi
|
|
292
|
-
|
|
293
|
-
backup_package_for_restore
|
|
294
|
-
write_version "$release_version"
|
|
295
|
-
read_pkg
|
|
296
|
-
log "(after release) version=$pkg_version"
|
|
297
|
-
fi
|
|
298
|
-
|
|
299
|
-
if [[ "$REQUIRE_TAG" == "1" ]]; then
|
|
300
|
-
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
301
|
-
die "--require-tag requires running inside a git repository"
|
|
302
|
-
fi
|
|
303
|
-
git_tag="$(git describe --tags --exact-match 2>/dev/null || true)"
|
|
304
|
-
if [[ -z "${git_tag:-}" ]]; then
|
|
305
|
-
die "--require-tag expected a semver tag on HEAD (e.g. v$pkg_version); found none"
|
|
306
|
-
fi
|
|
307
|
-
tag_version="${git_tag#v}"
|
|
308
|
-
if ! is_semver "$tag_version"; then
|
|
309
|
-
die "--require-tag expected tag in vX.Y.Z (or X.Y.Z) form; got: $git_tag"
|
|
310
|
-
fi
|
|
311
|
-
if [[ "$tag_version" != "$pkg_version" ]]; then
|
|
312
|
-
die "--require-tag tag version mismatch: tag=$git_tag (=> $tag_version) but package version=$pkg_version"
|
|
313
|
-
fi
|
|
314
|
-
log "git_tag=$git_tag"
|
|
315
|
-
fi
|
|
316
|
-
|
|
317
|
-
if [[ "$PUBLISH" == "1" && "$pkg_version" == "0.0.0" ]]; then
|
|
318
|
-
die "refusing to publish version 0.0.0; bump version first"
|
|
319
|
-
fi
|
|
320
|
-
|
|
321
|
-
if [[ "$VERSION_UPDATE" != "1" ]]; then
|
|
322
|
-
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
323
|
-
if [[ "$SKIP_GIT_CLEAN_CHECK" != "1" ]]; then
|
|
324
|
-
dirty="$(git status --porcelain || true)"
|
|
325
|
-
if [[ -n "${dirty:-}" ]]; then
|
|
326
|
-
if [[ "$PUBLISH" == "1" ]]; then
|
|
327
|
-
die "git working tree is not clean; commit or stash changes before publishing"
|
|
328
|
-
fi
|
|
329
|
-
warn "git working tree is not clean (ok for dry-run):"
|
|
330
|
-
echo "$dirty" | sed -n '1,20p'
|
|
331
|
-
if [[ "$(echo "$dirty" | wc -l | tr -d ' ')" -gt 20 ]]; then
|
|
332
|
-
warn "(truncated)"
|
|
333
|
-
fi
|
|
334
|
-
fi
|
|
335
|
-
fi
|
|
336
|
-
else
|
|
337
|
-
warn "not a git repository; skip git clean check"
|
|
338
|
-
fi
|
|
339
|
-
fi
|
|
340
|
-
|
|
341
|
-
if [[ "$SKIP_SECRETS_SCAN" != "1" ]]; then
|
|
342
|
-
if command -v rg >/dev/null 2>&1; then
|
|
343
|
-
log "secrets scan (filenames only):"
|
|
344
|
-
secret_re='(_authToken\\s*=\\s*[^\\s#]{16,}|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{20,}|npm_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|AIzaSy[A-Za-z0-9_-]{35}|-----BEGIN [A-Z ]*PRIVATE KEY-----|Authorization:\\s*Bearer\\s+[^\\s<]{20,}|Bearer\\s+[A-Za-z0-9._-]{20,}|(api[_-]?key|token|password|secret)\\s*[:=]\\s*[\\x27\\\"][^\\x27\\\"<]{12,}[\\x27\\\"])'
|
|
345
|
-
matches="$(rg -i -l "$secret_re" . --glob '!node_modules/**' --glob '!.git/**' --glob '!dist/**' --glob '!.env' --glob '!.env.*' || true)"
|
|
346
|
-
if [[ -n "${matches:-}" ]]; then
|
|
347
|
-
if [[ "$PUBLISH" == "1" ]]; then
|
|
348
|
-
echo "$matches"
|
|
349
|
-
die "potential secret-like patterns found; inspect files above before publishing"
|
|
350
|
-
fi
|
|
351
|
-
warn "potential secret-like patterns found (dry-run continues):"
|
|
352
|
-
echo "$matches"
|
|
353
|
-
else
|
|
354
|
-
log "secrets scan: ok"
|
|
355
|
-
fi
|
|
356
|
-
else
|
|
357
|
-
warn "rg not found; skip secrets scan"
|
|
358
|
-
fi
|
|
359
|
-
fi
|
|
360
|
-
|
|
361
|
-
if [[ "$SKIP_WHOAMI" == "1" ]]; then
|
|
362
|
-
warn "npm whoami skipped (--skip-whoami)"
|
|
363
|
-
else
|
|
364
|
-
set +e
|
|
365
|
-
whoami_out="$(npm whoami 2>&1)"
|
|
366
|
-
whoami_code=$?
|
|
367
|
-
set -e
|
|
368
|
-
if [[ "$whoami_code" == "0" ]]; then
|
|
369
|
-
npm_user="$(printf '%s' "$whoami_out" | tr -d '\r\n')"
|
|
370
|
-
log "npm whoami: ok ($npm_user)"
|
|
371
|
-
else
|
|
372
|
-
warn "npm whoami failed:"
|
|
373
|
-
echo "$whoami_out" >&2
|
|
374
|
-
if [[ "$PUBLISH" == "1" ]]; then
|
|
375
|
-
warn "continue publish flow despite whoami failure; final publish result will be authoritative"
|
|
376
|
-
fi
|
|
377
|
-
fi
|
|
378
|
-
fi
|
|
379
|
-
|
|
380
|
-
if [[ "$PUBLISH" == "1" ]]; then
|
|
381
|
-
if [[ "$SKIP_WHOAMI" == "1" ]]; then
|
|
382
|
-
warn "publish ownership precheck skipped because --skip-whoami is enabled"
|
|
383
|
-
else
|
|
384
|
-
if [[ -z "${npm_user:-}" ]]; then
|
|
385
|
-
warn "publish ownership precheck skipped: npm username unavailable"
|
|
386
|
-
else
|
|
387
|
-
check_publish_ownership "$npm_user" "$pkg_name"
|
|
388
|
-
fi
|
|
389
|
-
fi
|
|
390
|
-
fi
|
|
391
|
-
|
|
392
|
-
log "pack preview: npm pack --dry-run"
|
|
393
|
-
pack_out=""
|
|
394
|
-
set +e
|
|
395
|
-
pack_out="$(npm pack --dry-run 2>&1)"
|
|
396
|
-
pack_code=$?
|
|
397
|
-
set -e
|
|
398
|
-
if [[ "$pack_code" != "0" ]]; then
|
|
399
|
-
if echo "$pack_out" | grep -Eqi "(EPERM|EACCES)" && [[ "${npm_config_cache:-${NPM_CONFIG_CACHE:-}}" != "$DEFAULT_NPM_CACHE_DIR" ]]; then
|
|
400
|
-
warn "npm pack failed with EPERM/EACCES; retry with npm_config_cache=$DEFAULT_NPM_CACHE_DIR"
|
|
401
|
-
export npm_config_cache="$DEFAULT_NPM_CACHE_DIR"
|
|
402
|
-
mkdir -p "$DEFAULT_NPM_CACHE_DIR" 2>/dev/null || true
|
|
403
|
-
npm pack --dry-run >/dev/null
|
|
404
|
-
else
|
|
405
|
-
echo "$pack_out" >&2
|
|
406
|
-
exit "$pack_code"
|
|
407
|
-
fi
|
|
408
|
-
fi
|
|
409
|
-
log "pack preview: ok"
|
|
410
|
-
|
|
411
|
-
publish_cmd=(npm publish)
|
|
412
|
-
if [[ "$pkg_name" == @*/* ]]; then
|
|
413
|
-
publish_cmd=(npm publish --access public)
|
|
414
|
-
fi
|
|
415
|
-
log "publish command: ${publish_cmd[*]}"
|
|
416
|
-
|
|
417
|
-
if [[ "$PUBLISH" != "1" ]]; then
|
|
418
|
-
if [[ "$VERSION_UPDATE" == "1" ]]; then
|
|
419
|
-
if [[ "$YES" != "1" ]]; then
|
|
420
|
-
echo ""
|
|
421
|
-
echo "About to prepare git release:"
|
|
422
|
-
echo " - set version to $release_version"
|
|
423
|
-
echo " - git commit: chore(release): $release_tag_name"
|
|
424
|
-
echo " - git tag: $release_tag_name"
|
|
425
|
-
echo ""
|
|
426
|
-
read -r -p "Type 'release' to continue: " confirm_release
|
|
427
|
-
if [[ "${confirm_release:-}" != "release" ]]; then
|
|
428
|
-
die "aborted"
|
|
429
|
-
fi
|
|
430
|
-
fi
|
|
431
|
-
if ! git diff --quiet -- package.json; then
|
|
432
|
-
git add package.json
|
|
433
|
-
git commit -m "chore(release): $release_tag_name"
|
|
434
|
-
else
|
|
435
|
-
log "release: no version changes to commit"
|
|
436
|
-
fi
|
|
437
|
-
git tag "$release_tag_name"
|
|
438
|
-
log "release: created git tag $release_tag_name"
|
|
439
|
-
release_restore_on_exit=0
|
|
440
|
-
log "prepare complete (no npm publish performed)"
|
|
441
|
-
exit 0
|
|
442
|
-
fi
|
|
443
|
-
log "dry-run complete (no publish performed)"
|
|
444
|
-
exit 0
|
|
445
|
-
fi
|
|
446
|
-
|
|
447
|
-
if [[ "$pkg_private" == "true" ]]; then
|
|
448
|
-
die "package.json has private=true; set private=false before publishing"
|
|
449
|
-
fi
|
|
450
|
-
|
|
451
|
-
if [[ "$YES" != "1" ]]; then
|
|
452
|
-
echo ""
|
|
453
|
-
echo "About to publish to npm:"
|
|
454
|
-
echo " - $pkg_name@$pkg_version"
|
|
455
|
-
if [[ "$VERSION_UPDATE" == "1" ]]; then
|
|
456
|
-
echo ""
|
|
457
|
-
echo "And prepare git release:"
|
|
458
|
-
echo " - git commit: chore(release): $release_tag_name"
|
|
459
|
-
echo " - git tag: $release_tag_name"
|
|
460
|
-
fi
|
|
461
|
-
echo ""
|
|
462
|
-
read -r -p "Type 'publish' to continue: " confirm_publish
|
|
463
|
-
if [[ "${confirm_publish:-}" != "publish" ]]; then
|
|
464
|
-
die "aborted"
|
|
465
|
-
fi
|
|
466
|
-
fi
|
|
467
|
-
|
|
468
|
-
if [[ "$VERSION_UPDATE" == "1" ]]; then
|
|
469
|
-
if ! git diff --quiet -- package.json; then
|
|
470
|
-
git add package.json
|
|
471
|
-
git commit -m "chore(release): $release_tag_name"
|
|
472
|
-
else
|
|
473
|
-
log "release: no version changes to commit"
|
|
474
|
-
fi
|
|
475
|
-
git tag "$release_tag_name"
|
|
476
|
-
log "release: created git tag $release_tag_name"
|
|
477
|
-
release_restore_on_exit=0
|
|
478
|
-
fi
|
|
479
|
-
|
|
480
|
-
"${publish_cmd[@]}"
|
|
481
|
-
log "publish complete"
|