@jspg-ai/coding-bb 0.0.1
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/README.md +41 -0
- package/cbb/dev-standards/rules/cbb-ai-behavior.md +104 -0
- package/cbb/dev-standards/rules/cbb-coding-rule.md +50 -0
- package/cbb/dev-standards/rules/cbb-priority.md +56 -0
- package/cbb/dev-standards/skills/architecture-specs/SKILL.md +129 -0
- package/cbb/dev-standards/skills/coding-specs/SKILL.md +376 -0
- package/cbb/dev-standards/skills/coding-specs/references/concurrency.md +53 -0
- package/cbb/dev-standards/skills/coding-specs/references/config-center.md +16 -0
- package/cbb/dev-standards/skills/coding-specs/references/distributed.md +42 -0
- package/cbb/dev-standards/skills/coding-specs/references/es-coding.md +60 -0
- package/cbb/dev-standards/skills/coding-specs/references/scheduled-task.md +25 -0
- package/cbb/dev-standards/skills/coding-specs/references/security.md +11 -0
- package/cbb/dev-standards/skills/coding-specs/references/unit-testing.md +43 -0
- package/cbb/dev-standards/skills/es-design-specs/SKILL.md +104 -0
- package/cbb/dev-standards/skills/mysql-design-specs/SKILL.md +89 -0
- package/cbb/lib/install/claude-code.js +20 -0
- package/cbb/lib/install/cleanup.js +64 -0
- package/cbb/lib/install/init.js +1875 -0
- package/cbb/lib/install/qoder.js +20 -0
- package/cbb/lib/install/workspaces.js +232 -0
- package/cbb/lib/openspec/index.js +554 -0
- package/cbb/lib/superpowers/index.js +265 -0
- package/cbb/lib/utils/check-update.js +147 -0
- package/cbb/lib/utils/checkbox.js +383 -0
- package/cbb/lib/utils/gitignore.js +69 -0
- package/cbb/lib/utils/output.js +64 -0
- package/cbb/lib/utils/settings.js +119 -0
- package/cbb/lib/utils/version.js +135 -0
- package/cbb/lib/wiki/api-client.js +358 -0
- package/cbb/lib/wiki/cli.js +427 -0
- package/cbb/lib/wiki/convert.js +464 -0
- package/cbb/lib/wiki/index.js +218 -0
- package/cbb/lib/wiki/mermaid-guard.js +103 -0
- package/cbb/lib/wiki/split.js +131 -0
- package/cbb/tools/cbb-decompile-jar/SKILL.md +220 -0
- package/cbb/tools/cbb-decompile-jar/scripts/decompile.py +863 -0
- package/cbb/tools/cbb-design-to-wiki/SKILL.md +180 -0
- package/cbb/tools/cbb-mvn-guide/SKILL.md +281 -0
- package/cbb/tools/cbb-mvn-guide/scripts/mvn_jdk_manager.py +378 -0
- package/cbb/tools/cbb-mvn-guide/scripts/mvn_pom_jdk_reader.py +197 -0
- package/cbb/tools/cbb-plantuml-authoring/SKILL.md +448 -0
- package/cbb/tools/cbb-plantuml-authoring/references/drawing-templates.md +479 -0
- package/cbb/tools/cbb-wiki-ops/SKILL.md +134 -0
- package/cbb/tools/cbb-wiki-ops/scripts/check-auth.js +44 -0
- package/cbb/worktrees/_shared/scripts/find-target-worktree.js +96 -0
- package/cbb/worktrees/_shared/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/_shared/scripts/push-core.js +136 -0
- package/cbb/worktrees/_shared/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/commands/close.md +64 -0
- package/cbb/worktrees/commands/extend.md +65 -0
- package/cbb/worktrees/commands/init.md +55 -0
- package/cbb/worktrees/commands/push.md +42 -0
- package/cbb/worktrees/skills/openspec-close-worktree/SKILL.md +449 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-env.js +76 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-unarchived.js +48 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/delete-branches.js +136 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/discover-apps.js +76 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-target-worktree.js +96 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/remove-worktrees.js +124 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/safety-check.js +174 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/SKILL.md +390 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-env.js +95 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-repos.js +98 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/create-branches-and-worktrees.js +135 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-target-worktree.js +96 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/install-ai.js +150 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/list-available-apps.js +88 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/push-core.js +136 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/sync-repos.js +93 -0
- package/cbb/worktrees/skills/openspec-init-worktree/SKILL.md +539 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/auto-open.js +136 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env-deep.js +87 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env.js +90 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-repos.js +171 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-branches.js +103 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-worktrees.js +154 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/generate-app-options.js +93 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/install-ai.js +177 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/parse-config.js +67 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-branches.js +101 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-core.js +136 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/sync-repos.js +95 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/update-gitignore.js +134 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/SKILL.md +307 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/commit-worktrees.js +269 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-target-worktree.js +96 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-core.js +136 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-worktrees.js +159 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/silence-popup.js +23 -0
- package/config/cbb.yaml +6 -0
- package/config/config.sample.json +16 -0
- package/config/openspec/config.yaml +28 -0
- package/config/openspec/schemas/spec-driven/schema.yaml +247 -0
- package/config/openspec/schemas/spec-driven/templates/design.md +475 -0
- package/config/openspec/schemas/spec-driven/templates/proposal.md +58 -0
- package/config/openspec/schemas/spec-driven/templates/spec.md +8 -0
- package/config/openspec/schemas/spec-driven/templates/tasks.md +9 -0
- package/config/workspaces.json +15 -0
- package/openspec/.version +6 -0
- package/openspec/commands/apply.md +182 -0
- package/openspec/commands/archive.md +223 -0
- package/openspec/commands/bulk-archive.md +334 -0
- package/openspec/commands/continue.md +112 -0
- package/openspec/commands/explore.md +206 -0
- package/openspec/commands/ff.md +111 -0
- package/openspec/commands/new.md +70 -0
- package/openspec/commands/onboard.md +555 -0
- package/openspec/commands/propose.md +157 -0
- package/openspec/commands/sync.md +256 -0
- package/openspec/commands/update.md +85 -0
- package/openspec/commands/verify.md +169 -0
- package/openspec/skills/openspec-apply-change/SKILL.md +187 -0
- package/openspec/skills/openspec-archive-change/SKILL.md +181 -0
- package/openspec/skills/openspec-bulk-archive-change/SKILL.md +338 -0
- package/openspec/skills/openspec-continue-change/SKILL.md +117 -0
- package/openspec/skills/openspec-explore/SKILL.md +342 -0
- package/openspec/skills/openspec-ff-change/SKILL.md +116 -0
- package/openspec/skills/openspec-new-change/SKILL.md +76 -0
- package/openspec/skills/openspec-onboard/SKILL.md +560 -0
- package/openspec/skills/openspec-propose/SKILL.md +162 -0
- package/openspec/skills/openspec-sync-specs/SKILL.md +261 -0
- package/openspec/skills/openspec-update-change/SKILL.md +90 -0
- package/openspec/skills/openspec-verify-change/SKILL.md +174 -0
- package/package.json +46 -0
- package/superpowers/.version +6 -0
- package/superpowers/skills/brainstorming/SKILL.md +250 -0
- package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -0
- package/superpowers/skills/brainstorming/scripts/helper.js +167 -0
- package/superpowers/skills/brainstorming/scripts/server.cjs +723 -0
- package/superpowers/skills/brainstorming/scripts/start-server.sh +209 -0
- package/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -0
- package/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/superpowers/skills/brainstorming/visual-companion.md +299 -0
- package/superpowers/skills/dispatching-parallel-agents/SKILL.md +167 -0
- package/superpowers/skills/executing-plans/SKILL.md +64 -0
- package/superpowers/skills/finishing-a-development-branch/SKILL.md +225 -0
- package/superpowers/skills/receiving-code-review/SKILL.md +205 -0
- package/superpowers/skills/requesting-code-review/SKILL.md +95 -0
- package/superpowers/skills/requesting-code-review/code-reviewer.md +181 -0
- package/superpowers/skills/subagent-driven-development/SKILL.md +568 -0
- package/superpowers/skills/subagent-driven-development/implementer-prompt.md +154 -0
- package/superpowers/skills/subagent-driven-development/re-review-prompt.md +115 -0
- package/superpowers/skills/subagent-driven-development/scripts/review-package +46 -0
- package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +40 -0
- package/superpowers/skills/subagent-driven-development/scripts/task-brief +41 -0
- package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
- package/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/superpowers/skills/systematic-debugging/SKILL.md +283 -0
- package/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/superpowers/skills/systematic-debugging/find-polluter.sh +72 -0
- package/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/superpowers/skills/systematic-debugging/test-academic.md +14 -0
- package/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/superpowers/skills/test-driven-development/SKILL.md +320 -0
- package/superpowers/skills/test-driven-development/writing-good-tests.md +198 -0
- package/superpowers/skills/using-git-worktrees/SKILL.md +167 -0
- package/superpowers/skills/using-superpowers/SKILL.md +63 -0
- package/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -0
- package/superpowers/skills/using-superpowers/references/codex-tools.md +108 -0
- package/superpowers/skills/using-superpowers/references/gemini-tools.md +63 -0
- package/superpowers/skills/using-superpowers/references/hermes-tools.md +56 -0
- package/superpowers/skills/using-superpowers/references/pi-tools.md +16 -0
- package/superpowers/skills/verification-before-completion/SKILL.md +120 -0
- package/superpowers/skills/writing-plans/SKILL.md +171 -0
- package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/superpowers/skills/writing-skills/SKILL.md +679 -0
- package/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
- package/superpowers/skills/writing-skills/render-graphs.js +169 -0
- package/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
jdk_manager.py — 跨平台迷你 JDK 版本管理器(纯标准库)。
|
|
4
|
+
|
|
5
|
+
供 cbb-mvn-guide skill 使用,替代 jabba,避免外部下载依赖。
|
|
6
|
+
注册表存 ~/.jdk-registry.json,记录已登记的 JDK。
|
|
7
|
+
|
|
8
|
+
子命令:
|
|
9
|
+
ls 列出已登记的 JDK(名 -> 路径 -> 主版本)
|
|
10
|
+
current 显示当前 JAVA_HOME 指向的 JDK
|
|
11
|
+
link <name> <jdk_home> 登记一个本机已装的 JDK(校验 bin/java 存在)
|
|
12
|
+
unlink <name> 移除登记
|
|
13
|
+
use <name> 输出当前 shell 的 env 设置语句(不直接 set,
|
|
14
|
+
由调用方在同一条 shell 命令里 eval/source)
|
|
15
|
+
scan [--adopt] 扫描常见目录列出已装 JDK;
|
|
16
|
+
--adopt 一键登记未登记的(显式触发,打印明细)
|
|
17
|
+
doctor [--prune] 检查注册表中路径是否仍有效;
|
|
18
|
+
--prune 清理失效登记
|
|
19
|
+
--shell <posix|powershell> 指定 use 输出格式;不指定则按平台自动判断
|
|
20
|
+
|
|
21
|
+
用法示例(POSIX):
|
|
22
|
+
python jdk_manager.py link corretto8 /Library/Java/JavaVirtualMachines/corretto-8/Contents/Home
|
|
23
|
+
eval "$(python jdk_manager.py use corretto8)"; mvn -v
|
|
24
|
+
|
|
25
|
+
用法示例(PowerShell):
|
|
26
|
+
python jdk_manager.py link corretto8 D:\\env\\jdk\\corretto-1.8
|
|
27
|
+
python jdk_manager.py use corretto8 | Invoke-Expression; mvn -v
|
|
28
|
+
|
|
29
|
+
归一化主版本号(供 ls 展示与匹配): 1.8 -> 8, 21 -> 21。
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# 抑制字节码缓存:本脚本作为工具直接调用、无复用价值,避免在 scripts/ 下生成 __pycache__/
|
|
33
|
+
import sys
|
|
34
|
+
if not getattr(sys, 'dont_write_bytecode', False):
|
|
35
|
+
sys.dont_write_bytecode = True
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import glob
|
|
39
|
+
import json
|
|
40
|
+
import os
|
|
41
|
+
import platform
|
|
42
|
+
import re
|
|
43
|
+
import subprocess
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
|
|
46
|
+
# 强制 stdout/stderr 用 UTF-8,避免 Windows 控制台(GBK)中文乱码
|
|
47
|
+
try:
|
|
48
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
49
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
REGISTRY_PATH = Path.home() / ".jdk-registry.json"
|
|
54
|
+
QUIET = False
|
|
55
|
+
|
|
56
|
+
# 扫描目录(按平台)。glob 模式,匹配到的目录若含 bin/java 则视为 JDK。
|
|
57
|
+
SCAN_PATTERNS_WIN = [
|
|
58
|
+
r"C:\Program Files\Java\*",
|
|
59
|
+
r"C:\Program Files\Eclipse Adoptium\*",
|
|
60
|
+
r"C:\Program Files\Amazon Corretto\*",
|
|
61
|
+
r"C:\Program Files\Zulu\*",
|
|
62
|
+
r"C:\Program Files\Microsoft\jdk\*",
|
|
63
|
+
r"D:\env\jdk\*",
|
|
64
|
+
r"D:\env\*\jdk*",
|
|
65
|
+
r"D:\Program Files\Java\*",
|
|
66
|
+
]
|
|
67
|
+
SCAN_PATTERNS_POSIX = [
|
|
68
|
+
"/Library/Java/JavaVirtualMachines/*/Contents/Home",
|
|
69
|
+
"/usr/lib/jvm/*",
|
|
70
|
+
os.path.expanduser("~/.sdkman/candidates/java/*"),
|
|
71
|
+
os.path.expanduser("~/.jdks/*"),
|
|
72
|
+
"/opt/homebrew/opt/openjdk*/libexec/openjdk.jdk/Contents/Home",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
# vendor 关键词(小写匹配),用于自动命名;顺序即优先级。
|
|
76
|
+
VENDOR_KEYWORDS = [
|
|
77
|
+
"corretto",
|
|
78
|
+
"temurin",
|
|
79
|
+
"adoptium",
|
|
80
|
+
"adoptopenjdk",
|
|
81
|
+
"zulu",
|
|
82
|
+
"graalvm",
|
|
83
|
+
"oracle",
|
|
84
|
+
"liberica",
|
|
85
|
+
"microsoft",
|
|
86
|
+
"openjdk",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def fail(msg):
|
|
91
|
+
"""失败退出。quiet 模式下静默退出(不打印 stderr),由调用方靠退出码判断后自行提示用户。"""
|
|
92
|
+
if not QUIET:
|
|
93
|
+
print(msg, file=sys.stderr)
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def normalize_major(raw):
|
|
98
|
+
"""版本字符串归一化主版本号:1.8.0_492->8, 21.0.10->21;无法解析返回 None。"""
|
|
99
|
+
if not raw:
|
|
100
|
+
return None
|
|
101
|
+
m = re.match(r"^1\.(\d+)", raw)
|
|
102
|
+
if m:
|
|
103
|
+
return m.group(1)
|
|
104
|
+
m = re.match(r"^(\d+)", raw)
|
|
105
|
+
if m:
|
|
106
|
+
return m.group(1)
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def java_bin_name():
|
|
111
|
+
return "java.exe" if platform.system() == "Windows" else "java"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def is_valid_jdk_home(home):
|
|
115
|
+
"""校验 home 下存在 bin/java(.exe)。"""
|
|
116
|
+
return (Path(home) / "bin" / java_bin_name()).is_file()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def detect_version_string(home):
|
|
120
|
+
"""跑 bin/java -version 拿原始版本字符串(如 21.0.10 / 1.8.0_492),失败返回 None。"""
|
|
121
|
+
exe = str(Path(home) / "bin" / java_bin_name())
|
|
122
|
+
try:
|
|
123
|
+
out = subprocess.run(
|
|
124
|
+
[exe, "-version"],
|
|
125
|
+
capture_output=True, text=True, timeout=15,
|
|
126
|
+
)
|
|
127
|
+
text = (out.stderr or "") + (out.stdout or "")
|
|
128
|
+
except Exception:
|
|
129
|
+
return None
|
|
130
|
+
m = re.search(r'version "([^"]+)"', text)
|
|
131
|
+
return m.group(1) if m else None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def detect_major(home):
|
|
135
|
+
"""跑 bin/java -version 拿主版本号,失败返回 None。"""
|
|
136
|
+
raw = detect_version_string(home)
|
|
137
|
+
return normalize_major(raw) if raw else None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def detect_vendor(home):
|
|
141
|
+
"""从路径关键词推断 vendor,用于自动命名(仅参考,不强求准确)。"""
|
|
142
|
+
h = str(home).lower()
|
|
143
|
+
for v in VENDOR_KEYWORDS:
|
|
144
|
+
if v in h:
|
|
145
|
+
# adoptium/adoptopenjdk 统一归到 temurin 命名
|
|
146
|
+
return "temurin" if v in ("adoptium", "adoptopenjdk") else v
|
|
147
|
+
return "jdk"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def load_registry():
|
|
151
|
+
if not REGISTRY_PATH.is_file():
|
|
152
|
+
return {}
|
|
153
|
+
try:
|
|
154
|
+
with REGISTRY_PATH.open("r", encoding="utf-8") as f:
|
|
155
|
+
return json.load(f)
|
|
156
|
+
except (OSError, json.JSONDecodeError):
|
|
157
|
+
return {}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def save_registry(reg):
|
|
161
|
+
try:
|
|
162
|
+
with REGISTRY_PATH.open("w", encoding="utf-8") as f:
|
|
163
|
+
json.dump(reg, f, ensure_ascii=False, indent=2)
|
|
164
|
+
except OSError as e:
|
|
165
|
+
fail(f"写注册表失败: {e}")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def unique_name(base_name, existing_names):
|
|
169
|
+
"""生成不冲突的登记名:base_name 被占用则加 _2/_3..."""
|
|
170
|
+
if base_name not in existing_names:
|
|
171
|
+
return base_name
|
|
172
|
+
i = 2
|
|
173
|
+
while f"{base_name}_{i}" in existing_names:
|
|
174
|
+
i += 1
|
|
175
|
+
return f"{base_name}_{i}"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def scan_patterns():
|
|
179
|
+
return SCAN_PATTERNS_WIN if platform.system() == "Windows" else SCAN_PATTERNS_POSIX
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def find_installed_jdks():
|
|
183
|
+
"""扫描常见目录,返回 [(abs_home, major, vendor), ...],去重并按路径排序。"""
|
|
184
|
+
seen = set()
|
|
185
|
+
found = []
|
|
186
|
+
for pattern in scan_patterns():
|
|
187
|
+
for cand in glob.glob(pattern):
|
|
188
|
+
cand = os.path.abspath(cand)
|
|
189
|
+
if cand in seen:
|
|
190
|
+
continue
|
|
191
|
+
if not os.path.isdir(cand):
|
|
192
|
+
continue
|
|
193
|
+
if not is_valid_jdk_home(cand):
|
|
194
|
+
continue
|
|
195
|
+
seen.add(cand)
|
|
196
|
+
major = detect_major(cand) or "?"
|
|
197
|
+
vendor = detect_vendor(cand)
|
|
198
|
+
found.append((cand, major, vendor))
|
|
199
|
+
found.sort(key=lambda x: x[0])
|
|
200
|
+
return found
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def cmd_ls(_args):
|
|
204
|
+
reg = load_registry()
|
|
205
|
+
if not reg:
|
|
206
|
+
print("(空,用 link 登记一个 JDK,或 scan --adopt 自动登记)")
|
|
207
|
+
return
|
|
208
|
+
print(f"{'名称':<24} {'主版本':<8} {'路径'}")
|
|
209
|
+
for name, home in reg.items():
|
|
210
|
+
major = detect_major(home) or "?"
|
|
211
|
+
print(f"{name:<24} {major:<8} {home}")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def cmd_current(_args):
|
|
215
|
+
home = os.environ.get("JAVA_HOME")
|
|
216
|
+
if not home:
|
|
217
|
+
print("(JAVA_HOME 未设置)")
|
|
218
|
+
return
|
|
219
|
+
print(f"JAVA_HOME={home}")
|
|
220
|
+
major = detect_major(home)
|
|
221
|
+
if major:
|
|
222
|
+
print(f"主版本: {major}")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def cmd_link(args):
|
|
226
|
+
home = os.path.abspath(args.jdk_home)
|
|
227
|
+
if not is_valid_jdk_home(home):
|
|
228
|
+
fail(f"无效的 JDK 路径(找不到 {java_bin_name()}): {home}")
|
|
229
|
+
reg = load_registry()
|
|
230
|
+
reg[args.name] = home
|
|
231
|
+
save_registry(reg)
|
|
232
|
+
major = detect_major(home) or "?"
|
|
233
|
+
print(f"已登记 {args.name} -> {home} (主版本 {major})")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def cmd_unlink(args):
|
|
237
|
+
reg = load_registry()
|
|
238
|
+
if args.name not in reg:
|
|
239
|
+
fail(f"未找到 {args.name}")
|
|
240
|
+
del reg[args.name]
|
|
241
|
+
save_registry(reg)
|
|
242
|
+
print(f"已移除 {args.name}")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def cmd_scan(args):
|
|
246
|
+
"""扫描常见目录列出已装 JDK;--adopt 一键登记未登记的。"""
|
|
247
|
+
installed = find_installed_jdks()
|
|
248
|
+
if not installed:
|
|
249
|
+
print("未在常见目录扫描到 JDK,请用 link 手动登记,或检查 JDK 安装位置。")
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
reg = load_registry()
|
|
253
|
+
registered_paths = {os.path.abspath(h) for h in reg.values()}
|
|
254
|
+
|
|
255
|
+
print(f"{'序号':<6} {'主版本':<8} {'vendor':<12} {'状态':<10} 路径")
|
|
256
|
+
newly = []
|
|
257
|
+
for idx, (home, major, vendor) in enumerate(installed, 1):
|
|
258
|
+
status = "已登记" if home in registered_paths else "未登记"
|
|
259
|
+
print(f"{idx:<6} {major:<8} {vendor:<12} {status:<10} {home}")
|
|
260
|
+
if home not in registered_paths:
|
|
261
|
+
newly.append((home, major, vendor))
|
|
262
|
+
|
|
263
|
+
if not args.adopt:
|
|
264
|
+
if newly:
|
|
265
|
+
print(f"\n共 {len(newly)} 个未登记,用 `scan --adopt` 一键登记。")
|
|
266
|
+
else:
|
|
267
|
+
print("\n扫描到的 JDK 均已登记。")
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
if not newly:
|
|
271
|
+
print("没有需要登记的新 JDK。")
|
|
272
|
+
return
|
|
273
|
+
added = []
|
|
274
|
+
for home, major, vendor in newly:
|
|
275
|
+
base = f"{vendor}{major}" if vendor != "jdk" else f"jdk{major}"
|
|
276
|
+
name = unique_name(base, set(reg.keys()) | {a[0] for a in added})
|
|
277
|
+
reg[name] = home
|
|
278
|
+
added.append((name, home, major))
|
|
279
|
+
save_registry(reg)
|
|
280
|
+
print(f"\n已登记 {len(added)} 个:")
|
|
281
|
+
for name, home, major in added:
|
|
282
|
+
print(f" {name} -> {home} (主版本 {major})")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def cmd_doctor(args):
|
|
286
|
+
"""检查注册表中路径是否仍有效;--prune 清理失效登记。"""
|
|
287
|
+
reg = load_registry()
|
|
288
|
+
if not reg:
|
|
289
|
+
print("注册表为空,无需检查。")
|
|
290
|
+
return
|
|
291
|
+
dead = [n for n, h in reg.items() if not is_valid_jdk_home(h)]
|
|
292
|
+
if not dead:
|
|
293
|
+
print(f"全部有效({len(reg)} 个)。")
|
|
294
|
+
return
|
|
295
|
+
print(f"发现 {len(dead)} 个失效登记:")
|
|
296
|
+
for n in dead:
|
|
297
|
+
print(f" {n} -> {reg[n]}")
|
|
298
|
+
if args.prune:
|
|
299
|
+
for n in dead:
|
|
300
|
+
del reg[n]
|
|
301
|
+
save_registry(reg)
|
|
302
|
+
print(f"已清理 {len(dead)} 个失效登记。")
|
|
303
|
+
else:
|
|
304
|
+
print("用 `doctor --prune` 清理。")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def detect_shell(explicit):
|
|
308
|
+
if explicit:
|
|
309
|
+
return explicit
|
|
310
|
+
if platform.system() == "Windows":
|
|
311
|
+
return "powershell"
|
|
312
|
+
return "posix"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def cmd_use(args):
|
|
316
|
+
reg = load_registry()
|
|
317
|
+
if args.name not in reg:
|
|
318
|
+
fail(f"未登记的 JDK: {args.name}(先 link 或 scan --adopt)。已登记: {list(reg.keys()) or '无'}")
|
|
319
|
+
home = reg[args.name]
|
|
320
|
+
if not is_valid_jdk_home(home):
|
|
321
|
+
fail(f"登记路径已失效(找不到 {java_bin_name()}): {home}(用 doctor --prune 清理)")
|
|
322
|
+
|
|
323
|
+
shell = detect_shell(args.shell)
|
|
324
|
+
bin_dir = str(Path(home) / "bin")
|
|
325
|
+
|
|
326
|
+
if shell == "powershell":
|
|
327
|
+
print(f'$env:JAVA_HOME = "{home}"')
|
|
328
|
+
print(f'$env:PATH = "{bin_dir};" + $env:PATH')
|
|
329
|
+
else:
|
|
330
|
+
print(f'export JAVA_HOME="{home}"')
|
|
331
|
+
print(f'export PATH="$JAVA_HOME/bin:$PATH"')
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def main():
|
|
335
|
+
parser = argparse.ArgumentParser(
|
|
336
|
+
description="跨平台迷你 JDK 版本管理器(替代 jabba,纯标准库)。"
|
|
337
|
+
)
|
|
338
|
+
parser.add_argument("-q", "--quiet", action="store_true",
|
|
339
|
+
help="失败时静默(只退出码非0,不打印 stderr),供 skill 调用")
|
|
340
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
341
|
+
|
|
342
|
+
p_ls = sub.add_parser("ls", help="列出已登记的 JDK")
|
|
343
|
+
p_ls.set_defaults(func=cmd_ls)
|
|
344
|
+
p_cur = sub.add_parser("current", help="显示当前 JAVA_HOME 指向的 JDK")
|
|
345
|
+
p_cur.set_defaults(func=cmd_current)
|
|
346
|
+
|
|
347
|
+
p_link = sub.add_parser("link", help="登记一个本机 JDK")
|
|
348
|
+
p_link.add_argument("name", help="JDK 名称,如 corretto8 / jdk21")
|
|
349
|
+
p_link.add_argument("jdk_home", help="JDK 根目录(JAVA_HOME)")
|
|
350
|
+
p_link.set_defaults(func=cmd_link)
|
|
351
|
+
|
|
352
|
+
p_unlink = sub.add_parser("unlink", help="移除登记")
|
|
353
|
+
p_unlink.add_argument("name")
|
|
354
|
+
p_unlink.set_defaults(func=cmd_unlink)
|
|
355
|
+
|
|
356
|
+
p_scan = sub.add_parser("scan", help="扫描常见目录列出已装 JDK")
|
|
357
|
+
p_scan.add_argument("--adopt", action="store_true",
|
|
358
|
+
help="一键登记扫描到的未登记 JDK(显式触发,打印明细)")
|
|
359
|
+
p_scan.set_defaults(func=cmd_scan)
|
|
360
|
+
|
|
361
|
+
p_doc = sub.add_parser("doctor", help="检查注册表中路径是否仍有效")
|
|
362
|
+
p_doc.add_argument("--prune", action="store_true", help="清理失效登记")
|
|
363
|
+
p_doc.set_defaults(func=cmd_doctor)
|
|
364
|
+
|
|
365
|
+
p_use = sub.add_parser("use", help="输出当前 shell 的 env 设置语句")
|
|
366
|
+
p_use.add_argument("name")
|
|
367
|
+
p_use.add_argument("--shell", choices=["posix", "powershell"],
|
|
368
|
+
help="指定输出格式,不传按平台判断")
|
|
369
|
+
p_use.set_defaults(func=cmd_use)
|
|
370
|
+
|
|
371
|
+
args = parser.parse_args()
|
|
372
|
+
global QUIET
|
|
373
|
+
QUIET = args.quiet
|
|
374
|
+
args.func(args)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if __name__ == "__main__":
|
|
378
|
+
main()
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
pom_jdk_reader.py — 跨平台读取 Maven pom.xml 的 <java.version> 并归一化主版本号。
|
|
4
|
+
|
|
5
|
+
逻辑固化,纯标准库,任何装了 Python 3 的机器都能跑。供 cbb-mvn-guide skill 调用,
|
|
6
|
+
避免每次临场写 shell 正则出错。
|
|
7
|
+
|
|
8
|
+
用法:
|
|
9
|
+
python pom_jdk_reader.py <pom路径> # 输出 JSON: {"raw","major","pom_path","interpolated"}
|
|
10
|
+
python pom_jdk_reader.py <pom路径> --major # 只输出主版本号(8/21/17...),无匹配输出空行
|
|
11
|
+
python pom_jdk_reader.py <pom路径> --raw # 只输出原始值(如 1.8 / 21)
|
|
12
|
+
|
|
13
|
+
归一化规则:
|
|
14
|
+
1.8 / 1.8.0_492 / 1.8.0_492-b08 -> 8 (以 1. 开头取第二段)
|
|
15
|
+
21 / 21.0.10 -> 21 (否则取首段)
|
|
16
|
+
其他无法解析 -> None
|
|
17
|
+
|
|
18
|
+
父 pom 定位(优先级递减):
|
|
19
|
+
1. 解析当前 pom 的 <parent><relativePath>(缺省默认 ../pom.xml),按相对路径找父 pom。
|
|
20
|
+
2. relativePath 指向的文件不存在时,回退到逐级向上目录找同名 pom.xml(兼容旧行为)。
|
|
21
|
+
3. 都找不到(且 <parent> 只有 groupId/version 无本地路径)→ 停,不联网拉远程父 pom。
|
|
22
|
+
|
|
23
|
+
属性插值(--resolve-props,默认开):
|
|
24
|
+
<java.version>${maven.compiler.release}</java.version> 这类值,会尝试从同 pom 的
|
|
25
|
+
<properties> 解析对应键。解析不到则保留原始 ${...} 字符串并标记 interpolated=false,
|
|
26
|
+
由 skill 决定是否问用户。失败不报错。
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# 抑制字节码缓存:本脚本作为工具直接调用、无复用价值,避免在 scripts/ 下生成 __pycache__/
|
|
30
|
+
import sys
|
|
31
|
+
if not getattr(sys, 'dont_write_bytecode', False):
|
|
32
|
+
sys.dont_write_bytecode = True
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import json
|
|
36
|
+
import os
|
|
37
|
+
import re
|
|
38
|
+
from xml.etree import ElementTree as ET
|
|
39
|
+
|
|
40
|
+
# 匹配 <java.version>值</java.version>,容忍空白与注释,取首个非空白内容
|
|
41
|
+
_VERSION_RE = re.compile(r"<java\.version>\s*([^<\s]+)")
|
|
42
|
+
|
|
43
|
+
# 匹配 <relativePath>值</relativePath>
|
|
44
|
+
_RELATIVE_PATH_RE = re.compile(r"<relativePath>\s*([^<\s]+)")
|
|
45
|
+
|
|
46
|
+
# ${property.name} 形式
|
|
47
|
+
_PROP_REF_RE = re.compile(r"\$\{([^}]+)\}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def normalize_major(raw):
|
|
51
|
+
"""把原始版本字符串归一化为主版本号字符串,无法解析返回 None。"""
|
|
52
|
+
if not raw:
|
|
53
|
+
return None
|
|
54
|
+
m = re.match(r"^1\.(\d+)", raw)
|
|
55
|
+
if m:
|
|
56
|
+
return m.group(1)
|
|
57
|
+
m = re.match(r"^(\d+)", raw)
|
|
58
|
+
if m:
|
|
59
|
+
return m.group(1)
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def extract_properties(content):
|
|
64
|
+
"""从 pom 文本中粗解析 <properties> 下的键值对,返回 dict。
|
|
65
|
+
|
|
66
|
+
pom 一般良构,但为容忍 BOM/注释/非闭合等,用正则而非 ET 解析 properties。
|
|
67
|
+
仅取 properties 块内 <key>value</key> 形式。
|
|
68
|
+
"""
|
|
69
|
+
props = {}
|
|
70
|
+
m = re.search(r"<properties>(.*?)</properties>", content, re.DOTALL)
|
|
71
|
+
if not m:
|
|
72
|
+
return props
|
|
73
|
+
block = m.group(1)
|
|
74
|
+
for pm in re.finditer(r"<([\w.\-]+)>\s*([^<]*?)\s*</\1>", block):
|
|
75
|
+
props[pm.group(1)] = pm.group(2).strip()
|
|
76
|
+
return props
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def interpolate(value, props):
|
|
80
|
+
"""对 ${key} 形式的值做属性插值,递归解析(最多 5 层防循环)。
|
|
81
|
+
|
|
82
|
+
返回 (resolved_value, interpolated_flag)。interpolated=True 表示发生过插值且最终值不含 ${}。
|
|
83
|
+
"""
|
|
84
|
+
if not value or "${" not in value:
|
|
85
|
+
return value, True
|
|
86
|
+
seen = 0
|
|
87
|
+
cur = value
|
|
88
|
+
while "${" in cur and seen < 5:
|
|
89
|
+
def repl(m):
|
|
90
|
+
return props.get(m.group(1), m.group(0))
|
|
91
|
+
new = _PROP_REF_RE.sub(repl, cur)
|
|
92
|
+
if new == cur:
|
|
93
|
+
break # 无可解析的引用,停
|
|
94
|
+
cur = new
|
|
95
|
+
seen += 1
|
|
96
|
+
interpolated = "${" not in cur
|
|
97
|
+
return cur, interpolated
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def find_parent_pom(content, current_pom):
|
|
101
|
+
"""根据 <parent><relativePath> 定位父 pom 路径;回退到逐级向上找同名 pom.xml。
|
|
102
|
+
|
|
103
|
+
返回父 pom 绝对路径,找不到返回 None。
|
|
104
|
+
"""
|
|
105
|
+
cur_dir = os.path.dirname(os.path.abspath(current_pom))
|
|
106
|
+
|
|
107
|
+
# 1. 解析 relativePath(缺省默认 ../pom.xml)
|
|
108
|
+
rp_match = _RELATIVE_PATH_RE.search(content)
|
|
109
|
+
relative = rp_match.group(1).strip() if rp_match else "../pom.xml"
|
|
110
|
+
parent_candidate = os.path.normpath(os.path.join(cur_dir, relative))
|
|
111
|
+
if os.path.isfile(parent_candidate):
|
|
112
|
+
return parent_candidate
|
|
113
|
+
|
|
114
|
+
# 2. 回退:逐级向上找同名 pom.xml(兼容旧行为与无 <parent> 标签的多模块)
|
|
115
|
+
current = os.path.abspath(current_pom)
|
|
116
|
+
while True:
|
|
117
|
+
parent_dir = os.path.dirname(os.path.dirname(current))
|
|
118
|
+
parent_pom = os.path.join(parent_dir, "pom.xml")
|
|
119
|
+
if parent_pom == current or not os.path.isfile(parent_pom):
|
|
120
|
+
return None
|
|
121
|
+
return parent_pom # 只回退一级,避免无限上溯;如需继续由递归调用处理
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def read_pom_version(pom_path, resolve_props=True):
|
|
125
|
+
"""
|
|
126
|
+
从 pom_path 起读 <java.version>;本 pom 没有则按 relativePath/上级目录找父 pom。
|
|
127
|
+
返回 (raw, major, found_pom_path, interpolated);都没找到返回 (None, None, pom_path, True)。
|
|
128
|
+
"""
|
|
129
|
+
current = os.path.abspath(pom_path)
|
|
130
|
+
if not os.path.isfile(current):
|
|
131
|
+
return None, None, current, True
|
|
132
|
+
|
|
133
|
+
visited = set()
|
|
134
|
+
while current and current not in visited:
|
|
135
|
+
visited.add(current)
|
|
136
|
+
try:
|
|
137
|
+
with open(current, "r", encoding="utf-8", errors="replace") as f:
|
|
138
|
+
content = f.read()
|
|
139
|
+
except OSError:
|
|
140
|
+
break
|
|
141
|
+
|
|
142
|
+
m = _VERSION_RE.search(content)
|
|
143
|
+
if m:
|
|
144
|
+
raw = m.group(1).strip()
|
|
145
|
+
if resolve_props and "${" in raw:
|
|
146
|
+
props = extract_properties(content)
|
|
147
|
+
raw, interpolated = interpolate(raw, props)
|
|
148
|
+
else:
|
|
149
|
+
interpolated = "${" not in raw
|
|
150
|
+
return raw, normalize_major(raw), current, interpolated
|
|
151
|
+
|
|
152
|
+
# 本 pom 无 <java.version>,找父 pom
|
|
153
|
+
parent = find_parent_pom(content, current)
|
|
154
|
+
if not parent or parent == current:
|
|
155
|
+
break
|
|
156
|
+
current = parent
|
|
157
|
+
|
|
158
|
+
return None, None, os.path.abspath(pom_path), True
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def main():
|
|
162
|
+
parser = argparse.ArgumentParser(
|
|
163
|
+
description="读取 Maven pom.xml 的 <java.version> 并归一化主版本号(跨平台)。"
|
|
164
|
+
)
|
|
165
|
+
parser.add_argument("pom", help="pom.xml 路径")
|
|
166
|
+
parser.add_argument(
|
|
167
|
+
"--major", action="store_true", help="只输出主版本号(如 8/21)"
|
|
168
|
+
)
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
"--raw", action="store_true", help="只输出原始值(如 1.8 / 21)"
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--no-resolve-props", action="store_true",
|
|
174
|
+
help="关闭 ${} 属性插值,保留原始引用字符串"
|
|
175
|
+
)
|
|
176
|
+
args = parser.parse_args()
|
|
177
|
+
|
|
178
|
+
raw, major, found_path, interpolated = read_pom_version(
|
|
179
|
+
args.pom, resolve_props=not args.no_resolve_props
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if args.major:
|
|
183
|
+
print(major if major is not None else "")
|
|
184
|
+
return
|
|
185
|
+
if args.raw:
|
|
186
|
+
print(raw if raw is not None else "")
|
|
187
|
+
|
|
188
|
+
print(json.dumps({
|
|
189
|
+
"raw": raw,
|
|
190
|
+
"major": major,
|
|
191
|
+
"pom_path": found_path,
|
|
192
|
+
"interpolated": interpolated,
|
|
193
|
+
}, ensure_ascii=False))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
sys.exit(main())
|