@shundoo-ai/dsh-cosmic 1.0.2 → 1.0.4-alpha.0
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/package.json +1 -1
- package/skills/cosmic-build/skills/cosmic-vibe-coding/SKILL.md +63 -4
- package/skills/cosmic-build/skills/cosmic-vibe-coding/scripts/cosmic-hotdeploy.ps1 +257 -0
- package/skills/cosmic-dev-best-practices/references/cosmic-hotdeploy-build-slow-windows.md +38 -4
- package/skills/erp-analysis/scripts/init-python.ps1 +22 -0
- package/skills/erp-analysis/scripts/init-python.sh +19 -0
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ description: >
|
|
|
10
10
|
| 职责 | 内容 | 载体 |
|
|
11
11
|
|------|------|------|
|
|
12
12
|
| ① 开启热部署 | 开 `jar_deploy_enable` 开关、连 IDEA MCP、生成 `dev.json` | `scripts/dev.py` + `assets/dev.json` |
|
|
13
|
-
| ② 热加载 | 不重启、不碰 mc,把 jar 注入运行中 cosmic | `assets/kd-reloadjar-agent.jar
|
|
13
|
+
| ② 热加载 | 不重启、不碰 mc,把 jar 注入运行中 cosmic | `assets/kd-reloadjar-agent.jar`(**需先复制到机器级缓存再用**,见职责②§3) |
|
|
14
14
|
| ③ build + 热部署 | 构建 jar(MCP 定位编译错误)→ 热加载 | IDEA MCP / gradle + agent |
|
|
15
15
|
|
|
16
16
|
## 触发场景
|
|
@@ -42,6 +42,32 @@ description: >
|
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
+
## Windows:一条命令走完全程(推荐)
|
|
46
|
+
|
|
47
|
+
> 下面各职责给的是 **macOS/Linux** 命令。Windows 上**手拼这套流程代价极高** —— 实测一个 Windows 会话为跑通「构建 + 热部署」发了 **63 条 pwsh**、**6 次提权**(探测 PID → 找 JDK → 找 tools.jar → attach,每步都要单独授权)。
|
|
48
|
+
|
|
49
|
+
本 skill 随包提供封装脚本,**把全程收敛成一次调用 ⇒ 只需一次提权**:
|
|
50
|
+
|
|
51
|
+
```powershell
|
|
52
|
+
# 体检(不部署)
|
|
53
|
+
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1
|
|
54
|
+
# 构建 + 热部署
|
|
55
|
+
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Module <模块名>
|
|
56
|
+
# 干净重建 + 热部署
|
|
57
|
+
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Module <模块名> -Clean
|
|
58
|
+
# 只热部署已有 jar
|
|
59
|
+
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Jar <jar路径>
|
|
60
|
+
# 只构建、不部署
|
|
61
|
+
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Module <模块名> -WhatIf
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
内部依次完成:定位工作区 → 解析 preset bundle → 读 `dev.json` → **按端口反查 cosmic PID**(不是 `jps`,见职责②)→ 挑含 `tools.jar` 的 JDK → 构建 → Attacher 热加载。
|
|
65
|
+
|
|
66
|
+
参数:`-Module`|`-Jar`|`-Clean`|`-WhatIf`|`-LogFile`|`-Workspace`|`-PresetDir`|`-Port`。
|
|
67
|
+
工作区默认从「脚本位置的上两级」或当前目录推断;布局不同就传 `-Workspace <项目根>`。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
45
71
|
## 职责①:开启热部署(每个环境一次性)
|
|
46
72
|
|
|
47
73
|
1. **IDEA 启动 MCP(可选)**:JetBrains MCP,固定 URL `http://127.0.0.1:64342/stream`,用于**代码智能**(符号查找、调用分析、重构、即时查错)。⚠️ **非构建必需**——产 jar 走 gradle 命令行(见职责③),MCP 未配/未连不影响「build + 热部署」主线。
|
|
@@ -89,12 +115,36 @@ description: >
|
|
|
89
115
|
|
|
90
116
|
- 通用:`jps -l | grep kd.bos.service.webserver.JettyServer`(取 `appName=cosmic` 的那个)
|
|
91
117
|
- macOS 备选:`lsof -tiTCP:<port> -sTCP:LISTEN`
|
|
118
|
+
- **Windows 推荐(实测最可靠)**:按端口反查 + 命令行校验
|
|
119
|
+
|
|
120
|
+
```powershell
|
|
121
|
+
$conn = Get-NetTCPConnection -LocalPort <port> -State Listen | Select-Object -First 1
|
|
122
|
+
(Get-CimInstance Win32_Process -Filter "ProcessId=$($conn.OwningProcess)").CommandLine # 应含 kd.bos.service.webserver.JettyServer
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> ⚠️ **Windows 上 `jps` 看不到 cosmic**(实测):cosmic 常由 **SYSTEM** 账户启动,而 `jps` 只列举**当前用户**的 JVM → 输出里完全没有它,**极易误判「cosmic 没启动」**。用上面的方式零替代。
|
|
126
|
+
> 另:发现 PID 后先扫命令行里有没有 `-XX:+DisableAttachMechanism` —— 带此参数的 JVM **无法 attach**(硬阻断)。
|
|
127
|
+
> 跨用户 attach 本身**可用、无需提权**(Windows JVM attach 命名管道对 Administrators 组放行)。
|
|
92
128
|
|
|
93
129
|
### 3. 热部署
|
|
94
130
|
|
|
131
|
+
> ⚠️ **别直接用 preset 里的 `{SKILL_DIR}/assets/kd-reloadjar-agent.jar`** —— 先把 agent jar 复制到**机器级缓存**再用(`{AGENT}`,见下)。原因:Attacher 会把这个 jar **载入运行中的 cosmic JVM**,而 JVM 的 classloader 会**长期持有**该文件句柄;preset 一旦升级,包管理器要**整目录替换**(每个文件都得能删/改名)→ 必然在这个 jar 上失败(Windows 报 `os error 32`/拒绝访问)。
|
|
132
|
+
>
|
|
133
|
+
> `{AGENT}` = `$DSH_HOME/cache/cosmic/agent/kd-reloadjar-agent-<sha1前8位>.jar`(**带内容哈希命名**,所以新版永远不会覆盖可能已被 JVM 载入的旧副本)。`init-python.{sh,ps1}` 会预先复制;`cosmic-hotdeploy.ps1` 也会自动解析并复制。手工操作时:
|
|
134
|
+
|
|
95
135
|
```bash
|
|
96
|
-
|
|
97
|
-
|
|
136
|
+
# 先把 preset 里的原件复制到机器级缓存(哈希命名),再用缓存路径
|
|
137
|
+
AGENT_DIR="${DSH_HOME:-$HOME/.dsh}/cache/cosmic/agent"
|
|
138
|
+
SRC="{SKILL_DIR}/assets/kd-reloadjar-agent.jar"
|
|
139
|
+
HASH=$( (shasum -a 1 "$SRC" 2>/dev/null || sha1sum "$SRC") | cut -c1-8 )
|
|
140
|
+
AGENT="$AGENT_DIR/kd-reloadjar-agent-$HASH.jar"
|
|
141
|
+
mkdir -p "$AGENT_DIR" && [ -f "$AGENT" ] || cp "$SRC" "$AGENT"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# 用缓存副本调 Attacher(Windows 上把 : 换成 ;)
|
|
146
|
+
<java_home>/bin/java -cp <java_home>/lib/tools.jar:"$AGENT" kd.reloadjar.Attacher \
|
|
147
|
+
<cosmicPID> "$AGENT" \
|
|
98
148
|
"<jar绝对路径1>,<jar绝对路径2>" \
|
|
99
149
|
<account_id> <tenant_id> \
|
|
100
150
|
<可选logFile绝对路径>
|
|
@@ -104,6 +154,7 @@ description: >
|
|
|
104
154
|
- jar 路径可直接用构建产物 `build/libs/xxx.jar`,**无需拷到 lib/cus**。
|
|
105
155
|
- 最后一个参数 `logFile` **可选**:填了则在开发机侧写成功/失败标记日志(`status=SUCCESS/FAILED`)。
|
|
106
156
|
- classpath 分隔符:macOS/Linux 用 `:`,Windows 用 `;`。
|
|
157
|
+
- **Windows 直接用**「Windows:一条命令走完全程」里的 `cosmic-hotdeploy.ps1`(它已内置上述缓存逻辑)。
|
|
107
158
|
|
|
108
159
|
### 4. 验证
|
|
109
160
|
|
|
@@ -131,6 +182,13 @@ GRADLE_USER_HOME=<cwd>/.gradle/user-home "$GRADLE" :<模块>:build -x test
|
|
|
131
182
|
- 产物在 `<模块>/build/libs/*.jar`(本环境 `code/yun/sd-khxty-yun/build/libs/sd-khxty-yun-1.0.0.jar`)。
|
|
132
183
|
- ⚠️ 产 jar **只认 gradle 命令行**:IDEA MCP `build_project` 默认增量、源码未变会跳过(`isSuccess:true` 但 jar 不更新);勿用 `buildJar`/`deployJar`(那是冷启动拷贝到 lib/cus,热部署不需要)。
|
|
133
184
|
|
|
185
|
+
> **Windows 必读(实测踩坑,失败一次排查很久)**
|
|
186
|
+
> - **必须显式设 `JAVA_HOME` 指向 JDK(不是 JRE)**:Windows 注册表 `HKLM\SOFTWARE\JavaSoft\Java Development Kit` 的 `JavaHome` 会被 JRE 安装包改写成 `...\jre1.8.0_481`,Gradle 采信它 → `java.home` 指向 JRE → 报 `Could not find tools.jar`。显式 `$env:JAVA_HOME="C:\Program Files\Java\jdk1.8.0_XXX"`
|
|
187
|
+
> - **Gradle 版本必须按 `gradle/wrapper/gradle-wrapper.properties` 锁定**,不要「取 `~/.gradle/wrapper/dists` 下最新」—— 机器上可能同时装了要求 Java 22 的新版 Gradle,会直接把构建带偏。
|
|
188
|
+
> - ⚠️ **「伪成功构建」陷阱**:`compileJava` 处于 `UP-TO-DATE` 时 Gradle **根本不加载编译器**,缺 tools.jar 也不报错 → 看到 `BUILD SUCCESSFUL` + jar 正常产出,**但里面是旧字节码**。首次接入务必 `clean build` 验证一次真编译。
|
|
189
|
+
>
|
|
190
|
+
> 完整分析见 `cosmic-dev-best-practices/references/cosmic-hotdeploy-build-slow-windows.md` §4.3 / §4.7。Windows 上直接用上面「Windows:一条命令走完全程」的封装脚本即可,它已内置这些约束。
|
|
191
|
+
|
|
134
192
|
### 2. 定位错误 / 理解代码(IDEA MCP 代码智能,可选加分项)
|
|
135
193
|
|
|
136
194
|
> 此节可选:MCP 未配/未连不影响「build + 热部署」主线(gradle stderr 本身也会报编译错误,含文件名+行号)。
|
|
@@ -181,4 +239,5 @@ agent(`kd.reloadjar.ReloadAgent`)在 cosmic 进程内自动执行这两步
|
|
|
181
239
|
|------|------|------|
|
|
182
240
|
| `assets/dev.json` | ① 开启热部署 | 空配置模板(`dev.py` 读它作字段基础,生成工作区 `.dsh/cosmic/config/dev.json`) |
|
|
183
241
|
| `scripts/dev.py` | ① 开启热部署 | 环境配置脚本(`init`=读 `.kd/config.json` 覆盖 dev.json;`read`=只读;`__file__` 反推 skill 根) |
|
|
184
|
-
| `
|
|
242
|
+
| `scripts/cosmic-hotdeploy.ps1` | ②③ 热加载 / 构建+热部署 | **Windows 一键封装**(可移植:工作区自动推断、无硬编码路径):体检 / `-Module` 构建+热部署 / `-Clean` / `-Jar` 直传 / `-WhatIf`。见「Windows:一条命令走完全程」 |
|
|
243
|
+
| `assets/kd-reloadjar-agent.jar` | ② 热加载 | 热重载 agent(含 `kd.reloadjar.ReloadAgent` + `Attacher`)。⚠️ **原件不要直接加载** —— 先复制到 `$DSH_HOME/cache/cosmic/agent/kd-reloadjar-agent-<sha1前8位>.jar` 再用(避免 preset 升级时被 JVM 锁住,见职责②§3) |
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# cosmic-hotdeploy.ps1 -- Kingdee Cosmic build + hot-deploy (Windows, portable)
|
|
2
|
+
#
|
|
3
|
+
# PURPOSE
|
|
4
|
+
# Windows port of the cosmic-vibe-coding skill workflow (the skill docs give
|
|
5
|
+
# macOS/Linux commands). Discovers the running cosmic JVM, builds the module
|
|
6
|
+
# with the wrapper-pinned Gradle + a real JDK8, then hot-loads the jar via the
|
|
7
|
+
# Attacher agent -- no cosmic restart, no touching mc.
|
|
8
|
+
#
|
|
9
|
+
# USAGE
|
|
10
|
+
# pwsh -File cosmic-hotdeploy.ps1 # health check only
|
|
11
|
+
# pwsh -File cosmic-hotdeploy.ps1 -Module <name> # build + hot-deploy
|
|
12
|
+
# pwsh -File cosmic-hotdeploy.ps1 -Module <name> -Clean # clean build + hot-deploy
|
|
13
|
+
# pwsh -File cosmic-hotdeploy.ps1 -Jar a.jar,b.jar # hot-deploy existing jars
|
|
14
|
+
# pwsh -File cosmic-hotdeploy.ps1 -Module <name> -WhatIf # dry run (build only)
|
|
15
|
+
#
|
|
16
|
+
# PORTABILITY
|
|
17
|
+
# The workspace is inferred from this script's location (expects to live in
|
|
18
|
+
# <workspace>/runtime/); override with -Workspace. The skill bundle is located
|
|
19
|
+
# via $DSH_PROFILE_DIR or $DSH_HOME; override with -PresetDir. No other
|
|
20
|
+
# machine-specific paths are hard-coded -- the JDK is discovered from the
|
|
21
|
+
# running cosmic process, then from JAVA_HOME, then from common install roots.
|
|
22
|
+
#
|
|
23
|
+
# NOTE
|
|
24
|
+
# Windows classpath separator is ';' (not ':'). The Attacher must run on a JDK
|
|
25
|
+
# whose major version matches the target JVM (JDK8 + lib\tools.jar for cosmic).
|
|
26
|
+
|
|
27
|
+
[CmdletBinding()]
|
|
28
|
+
param(
|
|
29
|
+
[string[]]$Jar,
|
|
30
|
+
[string]$Module,
|
|
31
|
+
[switch]$Clean,
|
|
32
|
+
[switch]$WhatIf,
|
|
33
|
+
[string]$LogFile,
|
|
34
|
+
[string]$Workspace,
|
|
35
|
+
[string]$PresetDir,
|
|
36
|
+
[int]$Port
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
$ErrorActionPreference = 'Stop'
|
|
40
|
+
|
|
41
|
+
# ---- 0. Resolve workspace (no hard-coded user paths) ----
|
|
42
|
+
if (-not $Workspace) {
|
|
43
|
+
# Expect this script at <workspace>\runtime\cosmic-hotdeploy.ps1
|
|
44
|
+
if ($PSScriptRoot) {
|
|
45
|
+
$Workspace = Split-Path $PSScriptRoot -Parent
|
|
46
|
+
} else {
|
|
47
|
+
$Workspace = (Get-Location).Path
|
|
48
|
+
}
|
|
49
|
+
# If it was moved elsewhere, fall back to the cwd when that looks like a workspace.
|
|
50
|
+
if (-not (Test-Path (Join-Path $Workspace 'settings.gradle'))) {
|
|
51
|
+
if (Test-Path (Join-Path (Get-Location).Path 'settings.gradle')) {
|
|
52
|
+
$Workspace = (Get-Location).Path
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (-not (Test-Path (Join-Path $Workspace 'settings.gradle'))) {
|
|
57
|
+
throw "Workspace '$Workspace' has no settings.gradle. Pass -Workspace <project root>."
|
|
58
|
+
}
|
|
59
|
+
$cwd = (Resolve-Path $Workspace).Path
|
|
60
|
+
|
|
61
|
+
# ---- 0.1 Resolve the cosmic skill bundle ----
|
|
62
|
+
if (-not $PresetDir) {
|
|
63
|
+
$roots = @()
|
|
64
|
+
if ($env:DSH_PROFILE_DIR) { $roots += (Join-Path $env:DSH_PROFILE_DIR 'node_modules\@shundoo-ai\dsh-cosmic') }
|
|
65
|
+
if ($env:DSH_HOME) { $roots += (Join-Path $env:DSH_HOME '.agent-presets\cosmic') }
|
|
66
|
+
$PresetDir = $roots | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
|
|
67
|
+
}
|
|
68
|
+
if (-not $PresetDir) {
|
|
69
|
+
throw "Cannot locate the cosmic skill bundle. Set DSH_PROFILE_DIR/DSH_HOME, or pass -PresetDir <path containing skills\cosmic-build>."
|
|
70
|
+
}
|
|
71
|
+
$srcAgent = Join-Path $PresetDir 'skills\cosmic-build\skills\cosmic-vibe-coding\assets\kd-reloadjar-agent.jar'
|
|
72
|
+
if (-not (Test-Path $srcAgent)) { throw "Missing reload agent: $srcAgent" }
|
|
73
|
+
|
|
74
|
+
# ---- 0.2 Agent jar: use a machine-level, content-hashed COPY -- never load it from the preset ----
|
|
75
|
+
# Why: the Attacher loads this jar into the running cosmic JVM, and the JVM's classloader keeps
|
|
76
|
+
# the JarFile handle open for the JVM's lifetime. npm/pnpm upgrades replace the *whole package
|
|
77
|
+
# directory* (move the old dir away, put the new one in), which requires every file inside to be
|
|
78
|
+
# deletable/renamable -> the upgrade always fails on this jar (Windows: os error 32 / access
|
|
79
|
+
# denied). The old git-based layout never hit this because git only rewrites files whose content
|
|
80
|
+
# actually changed, and this jar rarely changes.
|
|
81
|
+
# Naming the copy with the content hash means a *new* preset version never overwrites an old copy
|
|
82
|
+
# that may already be loaded by a JVM.
|
|
83
|
+
$dshHome = if ($env:DSH_HOME) { $env:DSH_HOME }
|
|
84
|
+
elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dsh' }
|
|
85
|
+
elseif ($env:HOME) { Join-Path $env:HOME '.dsh' }
|
|
86
|
+
else { $null }
|
|
87
|
+
$agent = $srcAgent
|
|
88
|
+
if ($dshHome) {
|
|
89
|
+
$agentHash = (Get-FileHash $srcAgent -Algorithm SHA1).Hash.Substring(0, 8).ToLower()
|
|
90
|
+
$agentDir = Join-Path (Join-Path $dshHome 'cache\cosmic') 'agent'
|
|
91
|
+
$cachedAgent = Join-Path $agentDir "kd-reloadjar-agent-$agentHash.jar"
|
|
92
|
+
try {
|
|
93
|
+
if (-not (Test-Path $cachedAgent)) {
|
|
94
|
+
New-Item -ItemType Directory -Force -Path $agentDir | Out-Null
|
|
95
|
+
Copy-Item $srcAgent $cachedAgent -Force
|
|
96
|
+
}
|
|
97
|
+
$agent = $cachedAgent
|
|
98
|
+
# best-effort housekeeping: keep only the newest few hashed copies
|
|
99
|
+
Get-ChildItem $agentDir -Filter 'kd-reloadjar-agent-*.jar' -ErrorAction SilentlyContinue |
|
|
100
|
+
Sort-Object LastWriteTime -Descending | Select-Object -Skip 5 |
|
|
101
|
+
Remove-Item -Force -ErrorAction SilentlyContinue
|
|
102
|
+
} catch {
|
|
103
|
+
Write-Host "[agent] WARN cannot use the cached copy ($($_.Exception.Message)); falling back to the preset copy (preset upgrades may then hit a file lock)."
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
$devScript = Join-Path $PresetDir 'skills\cosmic-build\skills\cosmic-vibe-coding\scripts\dev.py'
|
|
107
|
+
|
|
108
|
+
# ---- 1. Read dev.json (env config produced by dev.py init) ----
|
|
109
|
+
$devJson = Join-Path $cwd '.dsh\cosmic\config\dev.json'
|
|
110
|
+
if (-not (Test-Path $devJson)) {
|
|
111
|
+
throw "Missing $devJson -- run 'dev.py init' first (enable hot-deploy / set ERP login)."
|
|
112
|
+
}
|
|
113
|
+
$dev = Get-Content $devJson -Raw | ConvertFrom-Json
|
|
114
|
+
|
|
115
|
+
# Port: explicit -Port wins, then dev.json, then the port embedded in erp_url.
|
|
116
|
+
$effPort = if ($Port) { $Port }
|
|
117
|
+
elseif ($dev.port) { [int]$dev.port }
|
|
118
|
+
elseif ($dev.erp_url -match '^https?://[^:/]+:(\d+)') { [int]$Matches[1] }
|
|
119
|
+
else { 80 }
|
|
120
|
+
$accountId = $dev.account_id
|
|
121
|
+
$tenantId = $dev.tenant_id
|
|
122
|
+
if (-not $accountId) { throw "dev.json has no account_id -- re-run 'dev.py init'." }
|
|
123
|
+
|
|
124
|
+
# ---- 2. Discover the cosmic PID by port ownership, then validate the JVM ----
|
|
125
|
+
$conn = Get-NetTCPConnection -LocalPort $effPort -State Listen -ErrorAction SilentlyContinue |
|
|
126
|
+
Select-Object -First 1
|
|
127
|
+
if (-not $conn) { throw "Nothing is listening on port $effPort -- is cosmic running?" }
|
|
128
|
+
$targetPid = $conn.OwningProcess
|
|
129
|
+
$proc = Get-CimInstance Win32_Process -Filter "ProcessId=$targetPid"
|
|
130
|
+
if ($proc.CommandLine -notmatch 'kd\.bos\.service\.webserver\.JettyServer') {
|
|
131
|
+
throw "Process $targetPid on port $effPort is not the cosmic JettyServer: $($proc.CommandLine)"
|
|
132
|
+
}
|
|
133
|
+
if ($proc.CommandLine -match 'DisableAttachMechanism') {
|
|
134
|
+
throw "Process $targetPid has -XX:+DisableAttachMechanism -- hot-deploy cannot attach to it."
|
|
135
|
+
}
|
|
136
|
+
$owner = ($proc | Invoke-CimMethod -MethodName GetOwner).User
|
|
137
|
+
|
|
138
|
+
# ---- 3. Pick the JDK: prefer the one the cosmic process itself runs on ----
|
|
139
|
+
# Derive JAVA_HOME from the process executable (reliable, unlike parsing the
|
|
140
|
+
# command line, which may quote "C:\path with spaces"\bin\java differently).
|
|
141
|
+
$candidates = @()
|
|
142
|
+
if ($proc.ExecutablePath) {
|
|
143
|
+
$javaBin = Split-Path $proc.ExecutablePath -Parent # ...\jdk\bin
|
|
144
|
+
if ((Split-Path $javaBin -Leaf) -ieq 'bin') { $candidates += (Split-Path $javaBin -Parent) }
|
|
145
|
+
}
|
|
146
|
+
if ($env:JAVA_HOME) { $candidates += $env:JAVA_HOME }
|
|
147
|
+
# Common JDK install roots across drives (best effort, machine independent).
|
|
148
|
+
foreach ($root in @("$env:ProgramFiles\Java", "${env:ProgramFiles(x86)}\Java", 'C:\Java',
|
|
149
|
+
'D:\Java', 'D:\Program Files\Java', "$env:USERPROFILE\.jdks")) {
|
|
150
|
+
if ($root -and (Test-Path $root)) {
|
|
151
|
+
$candidates += (Get-ChildItem $root -Directory -ErrorAction SilentlyContinue |
|
|
152
|
+
Where-Object { $_.Name -match '^jdk' } | ForEach-Object { $_.FullName })
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
$candidates = $candidates | Where-Object { $_ } | Select-Object -Unique
|
|
156
|
+
|
|
157
|
+
# A usable JDK for the Attacher has lib\tools.jar (JDK8) and bin\java.exe.
|
|
158
|
+
$attacherJdk = $candidates |
|
|
159
|
+
Where-Object { (Test-Path (Join-Path $_ 'lib\tools.jar')) -and (Test-Path (Join-Path $_ 'bin\java.exe')) } |
|
|
160
|
+
Select-Object -First 1
|
|
161
|
+
if (-not $attacherJdk) {
|
|
162
|
+
throw @"
|
|
163
|
+
No usable JDK for the Attacher (needs lib\tools.jar + bin\java.exe).
|
|
164
|
+
Candidates tried: $($candidates -join ' | ')
|
|
165
|
+
The target cosmic JVM needs a matching JDK whose major version matches it.
|
|
166
|
+
If cosmic runs on JDK8, install a JDK8 (not a JRE!) and pass -Workspace/-PresetDir as needed,
|
|
167
|
+
or set `$env:JAVA_HOME to a real JDK.
|
|
168
|
+
"@
|
|
169
|
+
}
|
|
170
|
+
$java = Join-Path $attacherJdk 'bin\java.exe'
|
|
171
|
+
$toolsJar = Join-Path $attacherJdk 'lib\tools.jar'
|
|
172
|
+
|
|
173
|
+
Write-Host "[env] workspace = $cwd"
|
|
174
|
+
Write-Host "[env] erp_url = $($dev.erp_url) (port $effPort)"
|
|
175
|
+
Write-Host "[env] cosmic PID = $targetPid (owner=$owner)"
|
|
176
|
+
Write-Host "[env] attacher JDK = $attacherJdk"
|
|
177
|
+
Write-Host "[env] accountId = $accountId / tenantId = $tenantId"
|
|
178
|
+
if ($agent -ne $srcAgent) {
|
|
179
|
+
Write-Host "[agent] $agent (cached copy; the preset original is never loaded)"
|
|
180
|
+
} else {
|
|
181
|
+
Write-Host "[agent] $agent (PRESET ORIGINAL -- preset upgrades may hit a file lock while cosmic runs)"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# ---- 4. Optional: build first ----
|
|
185
|
+
if ($Module) {
|
|
186
|
+
$gradleHome = Join-Path $cwd '.gradle\user-home'
|
|
187
|
+
|
|
188
|
+
# 4.1 Gradle version must follow the wrapper, never "newest dist" -- a newer
|
|
189
|
+
# dist (e.g. 8.x) may demand a newer Java runtime and derail this JDK8 chain.
|
|
190
|
+
$wrapperProps = Join-Path $cwd 'gradle\wrapper\gradle-wrapper.properties'
|
|
191
|
+
if (-not (Test-Path $wrapperProps)) { throw "Missing $wrapperProps." }
|
|
192
|
+
$m = [regex]::Match((Get-Content $wrapperProps -Raw), 'gradle-([0-9.]+)-bin')
|
|
193
|
+
if (-not $m.Success) { throw "Cannot parse the Gradle version from $wrapperProps." }
|
|
194
|
+
$distVer = $m.Groups[1].Value
|
|
195
|
+
$distRoot = Join-Path $env:USERPROFILE ".gradle\wrapper\dists\gradle-$distVer-bin"
|
|
196
|
+
$gradleBat = Get-ChildItem $distRoot -Recurse -Filter 'gradle.bat' -ErrorAction SilentlyContinue |
|
|
197
|
+
Select-Object -First 1
|
|
198
|
+
if (-not $gradleBat) {
|
|
199
|
+
throw "Gradle $distVer not found under $distRoot. Let the wrapper download it first (run .\gradlew.bat)."
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# 4.2 Build JDK: same preference order as above, but must carry tools.jar.
|
|
203
|
+
$buildJdk = $attacherJdk
|
|
204
|
+
if ($WhatIf) { Write-Host '[whatif] dry run: skipping the actual build.' }
|
|
205
|
+
|
|
206
|
+
$gradleArgs = @()
|
|
207
|
+
if ($Clean) { $gradleArgs += (':' + $Module + ':clean') }
|
|
208
|
+
$gradleArgs += (':' + $Module + ':build')
|
|
209
|
+
$gradleArgs += @('-x', 'test', '--console=plain')
|
|
210
|
+
Write-Host "[build] gradle $distVer | $($gradleArgs -join ' ')"
|
|
211
|
+
Write-Host "[build] JAVA_HOME=$buildJdk (GRADLE_USER_HOME=$gradleHome)"
|
|
212
|
+
$env:GRADLE_USER_HOME = $gradleHome
|
|
213
|
+
$env:JAVA_HOME = $buildJdk
|
|
214
|
+
& $gradleBat.FullName @gradleArgs
|
|
215
|
+
if ($LASTEXITCODE -ne 0) { throw "Gradle build failed (exit $LASTEXITCODE) -- see the compile errors above." }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# ---- 5. Resolve jars (auto-locate the build output when -Module was given) ----
|
|
219
|
+
if ((-not $Jar -or $Jar.Count -eq 0) -and $Module) {
|
|
220
|
+
$settings = Get-Content (Join-Path $cwd 'settings.gradle') -Raw
|
|
221
|
+
$mm = [regex]::Match($settings,
|
|
222
|
+
"project\(':$([regex]::Escape($Module))'\)\.projectDir\s*=\s*new File\('([^']+)'\)")
|
|
223
|
+
$moduleDir = if ($mm.Success) { Join-Path $cwd ($mm.Groups[1].Value -replace '/', '\') }
|
|
224
|
+
else { Join-Path $cwd "code\$Module" }
|
|
225
|
+
$libsDir = Join-Path $moduleDir 'build\libs'
|
|
226
|
+
$Jar = @(Get-ChildItem $libsDir -Filter '*.jar' -ErrorAction SilentlyContinue |
|
|
227
|
+
Where-Object { $_.Name -notlike '*sources*' -and $_.Name -notlike '*javadoc*' } |
|
|
228
|
+
Sort-Object LastWriteTime -Descending | Select-Object -First 1 |
|
|
229
|
+
ForEach-Object { $_.FullName })
|
|
230
|
+
if (-not $Jar) { throw "No build output jar in $libsDir." }
|
|
231
|
+
}
|
|
232
|
+
if (-not $Jar -or $Jar.Count -eq 0) {
|
|
233
|
+
Write-Host '[done] Health check only -- no hot-deploy (pass -Jar or -Module).'
|
|
234
|
+
exit 0
|
|
235
|
+
}
|
|
236
|
+
$jars = $Jar | ForEach-Object { (Resolve-Path $_).Path }
|
|
237
|
+
foreach ($j in $jars) {
|
|
238
|
+
Write-Host "[jar] $j ($([math]::Round((Get-Item $j).Length / 1KB)) KB)"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# ---- 6. Attach: save the jar bytes, then reload the ExtClassLoader ----
|
|
242
|
+
$attArgs = @(
|
|
243
|
+
'-cp', "$toolsJar;$agent",
|
|
244
|
+
'kd.reloadjar.Attacher',
|
|
245
|
+
$targetPid, $agent,
|
|
246
|
+
($jars -join ','),
|
|
247
|
+
$accountId, $tenantId
|
|
248
|
+
)
|
|
249
|
+
if ($LogFile) {
|
|
250
|
+
$dir = Split-Path $LogFile -Parent
|
|
251
|
+
if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
|
252
|
+
$attArgs += (Join-Path (Resolve-Path $dir).Path (Split-Path $LogFile -Leaf))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
& $java @attArgs
|
|
256
|
+
if ($LASTEXITCODE -ne 0) { throw "Hot-deploy failed (exit $LASTEXITCODE)." }
|
|
257
|
+
Write-Host '[done] Hot-deploy complete (SUCCESS = save + reset both applied).'
|
|
@@ -139,13 +139,25 @@ api fileTree(trd) {
|
|
|
139
139
|
`<cwd>/.dsh/cosmic/config/dev.json`:
|
|
140
140
|
|
|
141
141
|
```json
|
|
142
|
-
{ "java_home": "C:\\Program Files\\
|
|
142
|
+
{ "java_home": "C:\\Program Files\\Java\\jdk1.8.0_281" }
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
- 以 IDEA 项目 SDK
|
|
146
|
-
- Attacher 需与 cosmic 进程同 JDK **大版本**(JDK 8
|
|
145
|
+
- 以 IDEA 项目 SDK 为准(实测本机 = `C:\Program Files\Java\jdk1.8.0_281`,JDK 8,含 tools.jar)
|
|
146
|
+
- Attacher 需与 cosmic 进程同 JDK **大版本**(JDK 8,具体小版本无关;cosmic 自带
|
|
147
|
+
`F:\cosmic\xinghan8\jdk` = 1.8.0_212,与本机 1.8.0_281 混用无碍)
|
|
147
148
|
- `dev.py init` 自动发现不到时手动补;改后 `dev.py read` 验证
|
|
148
149
|
|
|
150
|
+
> ⚠️ **必设 `JAVA_HOME`,否则 `compileJava` 必失败**(实测踩坑):本机 Windows 注册表
|
|
151
|
+
> `HKLM\SOFTWARE\JavaSoft\Java Development Kit` 的 `JavaHome` 被 8u481 安装包改写成
|
|
152
|
+
> **JRE** 路径 `C:\Program Files\Java\jre1.8.0_481`。Gradle 在该值存在时优先采信它,
|
|
153
|
+
> 于是 `java.home` 指向 JRE → 没有 `tools.jar` → 报
|
|
154
|
+
> `Could not find tools.jar. Please check that ...\jre1.8.0_481 contains a valid JDK installation`。
|
|
155
|
+
> 显式 `$env:JAVA_HOME="C:\Program Files\Java\jdk1.8.0_281"` 即解(实测全量编译 10s 通过)。
|
|
156
|
+
>
|
|
157
|
+
> 🔎 **同源陷阱:「伪成功构建」**。若 `compileJava` 处于 `UP-TO-DATE`,Gradle 根本不加载
|
|
158
|
+
> 编译器,缺 tools.jar 也不报错 —— 你会看到 `BUILD SUCCESSFUL` + `jar` 正常产出,
|
|
159
|
+
> **但 jar 里是旧字节码**。首次接入务必用 `clean build` 验证一次真编译(本次就是这么暴露的)。
|
|
160
|
+
|
|
149
161
|
### 4.4 验证 classpath 是否真的干净(临时 init script)
|
|
150
162
|
|
|
151
163
|
`showcp.gradle`(用完可删):
|
|
@@ -205,11 +217,19 @@ Get-ChildItem "<lib>" -Recurse -Filter *.jar | ForEach-Object {
|
|
|
205
217
|
> 见 §0),须先单命令级放开 `danger-full-access`;`--no-daemon` 也救不了。
|
|
206
218
|
|
|
207
219
|
```powershell
|
|
208
|
-
$env:JAVA_HOME = "C:\Program Files\
|
|
220
|
+
$env:JAVA_HOME = "C:\Program Files\Java\jdk1.8.0_281" # 必须:注册表 java.home 指向 JRE(见 §4.3)
|
|
209
221
|
$gradle = "C:\Users\<user>\.gradle\wrapper\dists\gradle-7.6.3-bin\<hash>\gradle-7.6.3\bin\gradle.bat"
|
|
210
222
|
& $gradle ":<模块>:build" -x test --console=plain
|
|
211
223
|
```
|
|
212
224
|
|
|
225
|
+
> ⚠️ **Gradle 版本必须按 `gradle/wrapper/gradle-wrapper.properties` 锁定**,不要「取
|
|
226
|
+
> `~/.gradle/wrapper/dists` 下最新」。本机同时存在 `gradle-8.8-bin`,它要求 Java 22
|
|
227
|
+
> 运行时,与本项目 JDK 8 + tools.jar 链路不兼容,会直接把构建带偏。
|
|
228
|
+
>
|
|
229
|
+
> 🧩 本工作区已把整套流程封装为脚本:`runtime/cosmic-hotdeploy.ps1`
|
|
230
|
+
> (体检 / `-Jar` 热加载 / `-Module` 构建+热加载 / `-Clean`),内置上述 JDK、Gradle
|
|
231
|
+
> 版本、PID 发现、account/tenant 读取逻辑,可直接用。
|
|
232
|
+
|
|
213
233
|
> ❌ 不要加 `--no-daemon`:每次 fork 单次 Daemon(JVM 冷启动 + 重新加载配置 ≈ +30s)
|
|
214
234
|
> ✅ 复用常驻 Daemon(`org.gradle.daemon=true` 默认开启),改几行代码约 5-10 秒
|
|
215
235
|
|
|
@@ -314,6 +334,11 @@ for incremental compilation. See the debug log for more details.
|
|
|
314
334
|
### 6.1 发现 cosmic PID
|
|
315
335
|
|
|
316
336
|
```powershell
|
|
337
|
+
# ⭐ 方式零(推荐):按端口归属进程反查 + 命令行校验(实测最可靠)
|
|
338
|
+
$conn = Get-NetTCPConnection -LocalPort <cosmic_port> -State Listen | Select-Object -First 1
|
|
339
|
+
$cosmicPID = $conn.OwningProcess
|
|
340
|
+
(Get-CimInstance Win32_Process -Filter "ProcessId=$cosmicPID").CommandLine # 应含 kd.bos.service.webserver.JettyServer
|
|
341
|
+
|
|
317
342
|
# 方式一:HTTP 探测端口(最可靠,权限问题兜底)
|
|
318
343
|
Invoke-WebRequest http://127.0.0.1:<cosmic_port>/ierp # HTTP 200 = cosmic 活着
|
|
319
344
|
|
|
@@ -355,6 +380,15 @@ $jar = "<模块>\build\libs\<模块>-1.0.0.jar"
|
|
|
355
380
|
多次构建间隔短时用「最新时间戳 ±30s」窗口精确切分,避免混入前一次全量产物
|
|
356
381
|
8. **`dependencies --configuration compileClasspath` 对 fileTree 是惰性展开**,不一定显示 jar 明细,
|
|
357
382
|
要确认真实 classpath 必须用自定义任务打印 `sourceSets.main.compileClasspath.files`
|
|
383
|
+
9. **`jps` 看不到 cosmic 进程**(实测):本机 cosmic 由 **SYSTEM** 账户启动
|
|
384
|
+
(`F:\cosmic\xinghan8\jdk\bin\java ... -DappName=cosmic ... kd.bos.service.webserver.JettyServer`),
|
|
385
|
+
而 `jps` 只列举当前用户的 JVM → 输出里完全没有它,容易误判「cosmic 没启动」。
|
|
386
|
+
用 §6.1 方式零(按端口反查 OwningProcess)替代。
|
|
387
|
+
10. **跨用户 attach 是可用的,无需提权**:cosmic 跑在 SYSTEM 下、Attacher 由
|
|
388
|
+
`lihaisheng\administrator` 启动,实测 attach 成功(Windows JVM attach 命名管道对
|
|
389
|
+
Administrators 组放行),不必 `Start-Process -Verb RunAs`。
|
|
390
|
+
11. **`-XX:+DisableAttachMechanism` 是硬阻断**:本机 IDEA 侧另一个 JVM(`CosmicStudio-runnable.jar`)
|
|
391
|
+
带此参数,对它 attach 必然失败;发现 PID 后先扫命令行里有没有这个 flag。
|
|
358
392
|
9. **`java_home` 以 IDEA 项目 SDK 为准**(Windows 常见 `C:\Program Files\java\jdk`),
|
|
359
393
|
不要误填 cosmic 自带 JDK 目录;Attacher 只需与 cosmic 同 JDK 大版本(同为 JDK8 即兼容)
|
|
360
394
|
10. **gradle 构建命令不要加 `--no-daemon`**;多 IDLE Daemon 并存无害,`--stop` 可一次性清理
|
|
@@ -47,3 +47,25 @@ Write-Host "==> 验证导入"
|
|
|
47
47
|
uv run --python $VENV python -c "import pandas, numpy, plotly, jinja2, requests; print('OK', pandas.__version__, numpy.__version__, plotly.__version__, jinja2.__version__, 'requests', requests.__version__)"
|
|
48
48
|
|
|
49
49
|
Write-Host "==> init-python 完成"
|
|
50
|
+
|
|
51
|
+
# 顺带把热部署用的 agent jar 预复制到机器级缓存(与 venv 同理:**不要把会被外部进程独占
|
|
52
|
+
# 加载的文件留在 preset 里**)。Attacher 会把这个 jar 载入运行中的 cosmic JVM,JVM 长期持有
|
|
53
|
+
# 其句柄;preset 升级要整目录替换 → 会在这个 jar 上失败(Windows os error 32 / 拒绝访问)。
|
|
54
|
+
# 文件名带内容哈希:新版不会覆盖可能已被 JVM 载入的旧副本。幂等。
|
|
55
|
+
$AgentSrc = Join-Path $ROOT "skills\cosmic-build\skills\cosmic-vibe-coding\assets\kd-reloadjar-agent.jar"
|
|
56
|
+
if (Test-Path $AgentSrc) {
|
|
57
|
+
$AgentDir = Join-Path $DshHome "cache\cosmic\agent"
|
|
58
|
+
$AgentHash = (Get-FileHash $AgentSrc -Algorithm SHA1).Hash.Substring(0, 8).ToLower()
|
|
59
|
+
$AgentDst = Join-Path $AgentDir "kd-reloadjar-agent-$AgentHash.jar"
|
|
60
|
+
if (-not (Test-Path $AgentDir)) { New-Item -ItemType Directory -Force -Path $AgentDir | Out-Null }
|
|
61
|
+
if (Test-Path $AgentDst) {
|
|
62
|
+
Write-Host "==> agent 缓存已存在: $AgentDst"
|
|
63
|
+
} else {
|
|
64
|
+
Copy-Item $AgentSrc $AgentDst -Force
|
|
65
|
+
Write-Host "==> agent 已缓存: $AgentDst"
|
|
66
|
+
}
|
|
67
|
+
# 只保留最近 5 份哈希副本(best-effort)
|
|
68
|
+
Get-ChildItem $AgentDir -Filter "kd-reloadjar-agent-*.jar" -ErrorAction SilentlyContinue |
|
|
69
|
+
Sort-Object LastWriteTime -Descending | Select-Object -Skip 5 |
|
|
70
|
+
Remove-Item -Force -ErrorAction SilentlyContinue
|
|
71
|
+
}
|
|
@@ -48,3 +48,22 @@ echo "==> 验证导入"
|
|
|
48
48
|
uv run --python "$VENV" python -c 'import pandas, numpy, plotly, jinja2, requests; print("OK", pandas.__version__, numpy.__version__, plotly.__version__, jinja2.__version__, "requests", requests.__version__)'
|
|
49
49
|
|
|
50
50
|
echo "==> init-python 完成"
|
|
51
|
+
|
|
52
|
+
# 4) 顺带把热部署用的 agent jar 预复制到机器级缓存(与 venv 同理:**不要把会被外部进程
|
|
53
|
+
# 独占加载的文件留在 preset 里**)。Attacher 会把这个 jar 载入运行中的 cosmic JVM,
|
|
54
|
+
# JVM 长期持有其句柄;preset 升级要整目录替换 → 会在这个 jar 上失败(Windows os error 32)。
|
|
55
|
+
# 文件名带内容哈希:新版不会覆盖可能已被 JVM 载入的旧副本。幂等。
|
|
56
|
+
AGENT_SRC="$ROOT/skills/cosmic-build/skills/cosmic-vibe-coding/assets/kd-reloadjar-agent.jar"
|
|
57
|
+
if [ -f "$AGENT_SRC" ]; then
|
|
58
|
+
AGENT_DIR="$DSH_HOME/cache/cosmic/agent"
|
|
59
|
+
AGENT_HASH="$( (shasum -a 1 "$AGENT_SRC" 2>/dev/null || sha1sum "$AGENT_SRC") | cut -c1-8 )"
|
|
60
|
+
AGENT_DST="$AGENT_DIR/kd-reloadjar-agent-$AGENT_HASH.jar"
|
|
61
|
+
mkdir -p "$AGENT_DIR"
|
|
62
|
+
if [ -f "$AGENT_DST" ]; then
|
|
63
|
+
echo "==> agent 缓存已存在: $AGENT_DST"
|
|
64
|
+
else
|
|
65
|
+
cp "$AGENT_SRC" "$AGENT_DST" && echo "==> agent 已缓存: $AGENT_DST"
|
|
66
|
+
fi
|
|
67
|
+
# 只保留最近 5 份哈希副本(best-effort)
|
|
68
|
+
ls -1t "$AGENT_DIR"/kd-reloadjar-agent-*.jar 2>/dev/null | tail -n +6 | while read -r old; do rm -f "$old" 2>/dev/null || true; done
|
|
69
|
+
fi
|