dsh-ris-yds 0.0.0-stage → 1.1.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/LICENSE +21 -0
- package/README.md +160 -2
- package/assets/yds-chrome-resource-override.crx +0 -0
- package/bin/check-listing.mjs +247 -0
- package/bin/yds.js +19 -0
- package/cordis.patch.yml +13 -0
- package/lib/browser.js +428 -0
- package/lib/build.js +115 -0
- package/lib/cli.js +1130 -0
- package/lib/confcenter.js +377 -0
- package/lib/config.js +552 -0
- package/lib/fe-dev.js +206 -0
- package/lib/ide-proxy.js +906 -0
- package/lib/ide-watch.js +188 -0
- package/lib/ide.js +728 -0
- package/lib/index.js +267 -0
- package/lib/proc.js +270 -0
- package/lib/risd-peer.js +353 -0
- package/lib/rules.js +193 -0
- package/lib/socks.js +279 -0
- package/lib/token.js +36 -0
- package/lib/yms.js +336 -0
- package/lib/yoncode.js +249 -0
- package/native/yds-rust.darwin-arm64.node +0 -0
- package/native/yds-rust.darwin-x64.node +0 -0
- package/native/yds-rust.linux-arm64.node +0 -0
- package/native/yds-rust.linux-x64.node +0 -0
- package/native/yds-rust.win32-arm64.node +0 -0
- package/native/yds-rust.win32-x64.node +0 -0
- package/package.json +58 -4
- package/skills/dsh-ris-yds/SKILL.md +134 -0
- package/test/browser.test.mjs +442 -0
- package/test/cli-stop.test.mjs +40 -0
- package/test/cli.test.mjs +246 -0
- package/test/config.test.mjs +106 -0
- package/test/fe-dev.test.mjs +93 -0
- package/test/hints.test.mjs +13 -0
- package/test/ide-config.test.mjs +155 -0
- package/test/ide-proxy.test.mjs +256 -0
- package/test/ide-watch.test.mjs +79 -0
- package/test/launch-proxy.test.mjs +83 -0
- package/test/lease-release.test.mjs +76 -0
- package/test/mode-separation.test.mjs +147 -0
- package/test/pidfile.test.mjs +105 -0
- package/test/proc.test.mjs +172 -0
- package/test/proxy-standalone.test.mjs +121 -0
- package/test/risd-leases.test.mjs +111 -0
- package/test/risd-tier.test.mjs +361 -0
- package/test/rules-e2e.test.mjs +122 -0
- package/test/rules.test.mjs +154 -0
- package/test/yoncode.test.mjs +159 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-ris-yds contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,161 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-ris-yds
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
YonBIP YDS 客开后端的本地运行时,做成 **DSH 插件 + CLI** 双形态:
|
|
4
|
+
|
|
5
|
+
- **DSH 插件**:给 coding agent 提供一整套 `yds_*` 工具(体检 / 配置 / 凭据 / 运行时 / 集成 / 调试)。
|
|
6
|
+
- **CLI**(`bin/yds.js`,装包后 `yds` 命令):同一套能力给人用(在终端或 IDE 里执行)。
|
|
7
|
+
|
|
8
|
+
在本地把后端运行时跑起来,用本地实例替代远端环境做开发联调。
|
|
9
|
+
|
|
10
|
+
## 截图
|
|
11
|
+
|
|
12
|
+
| 在 DSH 里管理本地服务:启停、实时日志、调试入口 |
|
|
13
|
+
|---|
|
|
14
|
+
|  |
|
|
15
|
+
|
|
16
|
+
| 内网访问规则(可选):按程序 / 内网地址 / 端口放行,一次启用 |
|
|
17
|
+
|---|
|
|
18
|
+
|  |
|
|
19
|
+
|
|
20
|
+
## 能做什么
|
|
21
|
+
|
|
22
|
+
- **本地运行**:构建、启动、停止、查看状态与实时日志;启动失败自动回收,不留下半启动的进程。
|
|
23
|
+
- **平台配置**:从平台拉取配置并适配到本地运行时;支持离线缓存,网络不可达时也能启动。
|
|
24
|
+
- **内网访问**:数据库 / 缓存 / 内网服务可经代理或直连访问;本机其它客户端也能共用同一条隧道。dsh-ris-ide 的代理 daemon(risd)在跑时自动共用它的引擎,`yds stop` 只撤自己的规则、不打断别人
|
|
25
|
+
(需要系统权限的可选能力)。
|
|
26
|
+
- **平台凭据**:生成调用平台 API 的 YYCToken,新环境无需额外安装。
|
|
27
|
+
- **前端调试**:让远端页面加载本地前端资源,三种接入方式(浏览器扩展规则 / 请求改路 / URL 参数)。
|
|
28
|
+
- **IDE 面板**:在 dsh-ris-ide 面板里启停本地服务、查看实时日志、做 JDWP 断点调试。
|
|
29
|
+
- **外部 IDE**:本地运行时可被外部 IDE 直接 F5 启动调试。
|
|
30
|
+
- **浏览器回调校准**:确保工作台页面的请求回落到本机实例(失败时是静默的,自检可查)。
|
|
31
|
+
- **环境自检**:`yds doctor` 一次列出配置、运行时、端口、隧道等所有缺失项。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# 公开 npm(推荐)
|
|
37
|
+
dsh plugin --profile web add dsh-ris-yds
|
|
38
|
+
|
|
39
|
+
# 或从本地 checkout 安装(开发调试)
|
|
40
|
+
dsh plugin --profile web add <本目录>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
装完需**重启 `dsh web`**(宿主插件只在启动时组合一次,工具注册才生效)。新会话里 agent 就有
|
|
44
|
+
`yds_*` 工具;`skills/dsh-ris-yds` 会自动同步到项目 `.dsh/skills`。
|
|
45
|
+
|
|
46
|
+
前置:JDK 21 与 Maven 在 `PATH`(或在 `.env` 指定 `java.path` / `maven.settings`)。
|
|
47
|
+
|
|
48
|
+
## 快速开始
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd <你的 YonBIP 项目目录>
|
|
52
|
+
|
|
53
|
+
yds doctor # 体检:配置解析、运行时、端口、隧道/缺失项
|
|
54
|
+
yds confcenter # 拉取并适配平台配置(首次联网;之后可 --offline)
|
|
55
|
+
yds build # 首次构建运行时(下载依赖,约 19 分钟;缓存后约 1 分钟)
|
|
56
|
+
yds start # 后台启动(幂等;缺少配置/隧道会自动补)
|
|
57
|
+
yds status # 进程、端口、就绪状态
|
|
58
|
+
yds logs --lines 200
|
|
59
|
+
yds stop
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 命令一览
|
|
63
|
+
|
|
64
|
+
| 命令 | 能力 |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `yds doctor` / `yds config` / `yds token <url>` | 体检 / 生效配置(凭据打码)/ 生成平台 API 凭据 |
|
|
67
|
+
| `yds confcenter [--offline]` | 拉取并适配平台配置到本地运行时 |
|
|
68
|
+
| `yds build` | 构建本地运行时(首次下载依赖) |
|
|
69
|
+
| `yds start` / `yds stop [--keep-socks]` / `yds status` / `yds logs` | 本地运行时生命周期 |
|
|
70
|
+
| `yds ide apply\|check\|remove` | 本地运行时的 IDE 面板集成(启停 / 日志 / 调试) |
|
|
71
|
+
| `yds proxy [status\|sync\|start\|stop\|git-on\|git-off]` | 内网访问与 git 代理 |
|
|
72
|
+
| `yds browser [status\|fix] [--dry-run]` | 浏览器请求回调校准 |
|
|
73
|
+
| `yds fe [rules\|plan\|url <page>]` | 前端调试接入(扩展规则 / 请求改路 / URL 参数) |
|
|
74
|
+
|
|
75
|
+
## 配置(`.env` / 环境变量)
|
|
76
|
+
|
|
77
|
+
工作区根目录放一份 `.env` 即可,键名不带插件前缀;项目 `.vscode/settings.json` 的 `yds.*` 优先。
|
|
78
|
+
|
|
79
|
+
| 键 | 默认 | 说明 |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `socks.host` / `socks.port` | 空 | 都空 = 直连;有值 = 代理模式 |
|
|
82
|
+
| `socks.dbPort` / `socks.redisPort` | 1521 / 6890 | 本机映射端口(JDBC/Redis 客户端用) |
|
|
83
|
+
| `ide.launch` | `proxy` | `proxy` / `direct`:外部 IDE 走代理还是直连 |
|
|
84
|
+
| `git.proxy` | `auto` | `off` 关闭 git 代理 |
|
|
85
|
+
| `local.ymsHome` | `<项目>/yms-home` | 本地运行时目录 |
|
|
86
|
+
| `maven.settings` / `maven.repo` | 空 | 构建用;留空用默认设置 |
|
|
87
|
+
| `java.path` | 空 | 指定 JDK(留空用 PATH) |
|
|
88
|
+
|
|
89
|
+
## 两种使用方式:代理 / 直连
|
|
90
|
+
|
|
91
|
+
| | 代理模式(有 `socks.*`) | 直连模式(无 `socks.*`) |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| 内网访问 | 经隧道;本机端口映射给 JDBC/Redis | 直接访问,不起任何隧道 |
|
|
94
|
+
| 本地运行时配置 | 改写成 `127.0.0.1` 映射地址 | 平台地址原样保留 |
|
|
95
|
+
| git 代理 | 自动按克隆地址主机写入 | 不写 |
|
|
96
|
+
|
|
97
|
+
### 隧道从哪来(自动选择,无需配置)
|
|
98
|
+
|
|
99
|
+
1. **dsh-ris-ide 的 risd daemon 在跑**:规则、租约与系统层拦截交给它(写规则 + 登记租约),
|
|
100
|
+
daemon 的引擎不另起;但 YMS 配置被改写成 `127.0.0.1:<dbForwardPort>/<redisForwardPort>`,
|
|
101
|
+
而 daemon 引擎的 127.0.0.1 映射只做同端口映射,覆盖不到这两个端口,所以**内置 SOCKS 转发器
|
|
102
|
+
仍会作为兜底 127.0.0.1 映射同时启动**(代理模式下是两个进程:daemon 引擎 + 内置转发器;
|
|
103
|
+
直连模式下都不起);
|
|
104
|
+
2. 装了 dsh-ris-ide 但 daemon 没跑:借用它已有的代理引擎(127.0.0.1 映射由它按规则提供);
|
|
105
|
+
3. 都没有:用本插件内置的 SOCKS 转发器(零依赖,不需要 dsh-ris-ide)。
|
|
106
|
+
|
|
107
|
+
规则表是**全局一张表**(面板/文件只有一份,保存即整表覆盖,不区分 workspace)。yds 每次启动注入自己
|
|
108
|
+
那一段时是「保留非 yds 行 + 用本次的 yds 行整体替换旧的 yds 行」,所以 `yds stop` 会把表里**全部** yds 行
|
|
109
|
+
撤掉(同一台机器上的多个项目共用这张表,没有交叉配置);非 yds 行(你自己加的规则、其它工具注入的)不受影响。
|
|
110
|
+
非 Windows(含 WSL)除了 `java.exe`/`git.exe` 还会注入本地侧 `java`/`git` 两条规则(`side` 标明侧别),
|
|
111
|
+
WSL 里的 JVM/git 才会被拦截。`yds stop` **不会主动停掉机器级引擎**(IDE 可能还在用)。
|
|
112
|
+
输出里的「已从共享规则表移除 N 条规则/M 个 host」是本次真正减掉的部分,减掉 0 时会明确说没改动规则表。
|
|
113
|
+
注意 `yds stop` 释放的是本项目的租约:如果 daemon 的生命周期配置是「无租约即拆除代理」,而 yds 恰好是
|
|
114
|
+
最后一个租约持有者,daemon 会在约 300ms 后自己关闭隧道(这不是 yds 去停它,进程日志里会打出来)。
|
|
115
|
+
|
|
116
|
+
同一台机器可以混合:dsh 走代理、外部 IDE 直连(`.env` 里设 `ide.launch=direct`):
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# .env: 保留 socks.host / socks.port,加 ide.launch=direct
|
|
120
|
+
yds confcenter # 准备运行配置
|
|
121
|
+
yds ide apply # 应用当前模式
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
切换模式只需改 `ide.launch` 后重新应用。
|
|
125
|
+
|
|
126
|
+
## 与 dsh-ris-ide 搭配(可选)
|
|
127
|
+
|
|
128
|
+
装上 [`dsh-ris-ide`](https://gitee.com/riscloud/dsh-ris-ide) 后,可在同一个 dsh 里获得:
|
|
129
|
+
|
|
130
|
+
- 启停本地服务、查看实时日志(只看本次启动);
|
|
131
|
+
- JDWP 断点调试(线程、调用栈、变量);
|
|
132
|
+
- 工作区文件树、编辑器、搜索与多文件粘贴。
|
|
133
|
+
|
|
134
|
+
两者**互不依赖**,可各自单独安装:没装 dsh-ris-ide 时本插件各项能力照常可用。
|
|
135
|
+
|
|
136
|
+
## 安全与边界(摘要)
|
|
137
|
+
|
|
138
|
+
- `workbenchUrl` 是**项目级信任根**:只对可信仓库执行 `yds build` / `yds start`。
|
|
139
|
+
- 本机转发端口无鉴权(单用户开发机可接受)。
|
|
140
|
+
- `yds token` 明文输出短期凭据,避免落盘。
|
|
141
|
+
- 涉及浏览器状态的命令支持 `--dry-run` 预览,不确定时先预览。
|
|
142
|
+
|
|
143
|
+
完整清单与实现理由见 [DEVELOPMENT.md](DEVELOPMENT.md) §15。
|
|
144
|
+
|
|
145
|
+
## 开发
|
|
146
|
+
|
|
147
|
+
- 架构、模块、内部机制(配置管线、隧道、模式隔离、IDE 集成、校准、发版)见
|
|
148
|
+
**[DEVELOPMENT.md](DEVELOPMENT.md)**。
|
|
149
|
+
- 测试与自检:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm test # node --test "test/*.test.mjs"
|
|
153
|
+
npm run check:listing # 上架自检(❌ 必须为 0)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- 目录:`lib/` 实现、`bin/` CLI、`native/` 原生凭据模块(6 平台)、`assets/` 浏览器扩展包、
|
|
157
|
+
`test/` 测试、`skills/` dsh 技能。
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
|
Binary file
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 上架自检:把 dsh-market(awesome-dsh-plugin 目录)与 dsh.so 的收录条件,变成能在本地跑的检查。
|
|
4
|
+
*
|
|
5
|
+
* npm run check:listing
|
|
6
|
+
* npm run check:listing -- --json
|
|
7
|
+
* npm run check:listing -- --strict # 警告也算失败
|
|
8
|
+
* npm run check:listing -- --net # 额外尝试联网确认仓库/包公开可达(内网环境会失败)
|
|
9
|
+
*
|
|
10
|
+
* 规则来源与逐条解释在**仓库外**的插件开发手册(`docs/dsh-plugin-listing-guide.md`)。这里是**静态**检查:license 选哪个、仓库要不要公开、
|
|
11
|
+
* npm 包名怎么定,这些决定项脚本只能标记出来,得人定。
|
|
12
|
+
*/
|
|
13
|
+
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
14
|
+
import { existsSync } from 'node:fs'
|
|
15
|
+
import { join, dirname, extname, relative } from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
|
|
18
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
19
|
+
const argv = process.argv.slice(2)
|
|
20
|
+
const AS_JSON = argv.includes('--json')
|
|
21
|
+
const STRICT = argv.includes('--strict')
|
|
22
|
+
const NET = argv.includes('--net')
|
|
23
|
+
|
|
24
|
+
const results = []
|
|
25
|
+
const ok = (id, detail) => results.push({ id, level: 'ok', detail })
|
|
26
|
+
const warn = (id, detail) => results.push({ id, level: 'warn', detail })
|
|
27
|
+
const fail = (id, detail) => results.push({ id, level: 'error', detail })
|
|
28
|
+
|
|
29
|
+
async function readJson(rel) {
|
|
30
|
+
return JSON.parse(await readFile(join(ROOT, rel), 'utf8'))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readText(rel) {
|
|
34
|
+
try {
|
|
35
|
+
return await readFile(join(ROOT, rel), 'utf8')
|
|
36
|
+
} catch {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 递归列出目录下的文件(跳过 node_modules / .git / 构建产物) */
|
|
42
|
+
async function listFiles(dir, acc = []) {
|
|
43
|
+
let entries
|
|
44
|
+
try {
|
|
45
|
+
entries = await readdir(join(ROOT, dir), { withFileTypes: true })
|
|
46
|
+
} catch {
|
|
47
|
+
return acc
|
|
48
|
+
}
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist') continue
|
|
51
|
+
const rel = dir === '.' ? entry.name : `${dir}/${entry.name}`
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
await listFiles(rel, acc)
|
|
54
|
+
} else {
|
|
55
|
+
acc.push(rel)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return acc
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const SRC_EXT = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.sh', '.ps1', '.vue'])
|
|
62
|
+
const PUBLIC_HOSTS = ['github.com', 'gitee.com', 'gitlab.com']
|
|
63
|
+
|
|
64
|
+
function short(text, max = 120) {
|
|
65
|
+
const line = String(text).replace(/\s+/g, ' ').trim()
|
|
66
|
+
return line.length > max ? `${line.slice(0, max - 1)}…` : line
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function main() {
|
|
70
|
+
const pkg = await readJson('package.json')
|
|
71
|
+
const readme = await readText('README.md')
|
|
72
|
+
|
|
73
|
+
// ── dsh.so:插件 id(slug) ───────────────────────────────────────────────
|
|
74
|
+
const bareName = String(pkg.name || '').replace(/^@[^/]+\//, '')
|
|
75
|
+
if (/^[a-z0-9]+(-[a-z0-9]+)*$/.test(bareName)) ok('插件 id(slug)', `"${pkg.name}" → ${bareName} 匹配 ^[a-z0-9]+(-[a-z0-9]+)*$`)
|
|
76
|
+
else fail('插件 id(slug)', `"${pkg.name}" 不匹配 ^[a-z0-9]+(-[a-z0-9]+)*$:改成小写 kebab-case(scoped 包只校验 scope 后的段)`)
|
|
77
|
+
|
|
78
|
+
// ── 版本:市场展示的是最新正式版 ─────────────────────────────────────────
|
|
79
|
+
if (/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(String(pkg.version || ''))) {
|
|
80
|
+
if (String(pkg.version).includes('-')) warn('版本号', `${pkg.version} 是预发布版;市场/dsh.so 展示 latest 语义时应发布 X.Y.Z 正式版(快照只用于本地)`)
|
|
81
|
+
else ok('版本号', `${pkg.version}(合法 semver,正式版)`)
|
|
82
|
+
} else {
|
|
83
|
+
fail('版本号', `"${pkg.version}" 不是合法 semver`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── dsh.so:DSH 兼容信号(至少一个) ─────────────────────────────────────
|
|
87
|
+
const signals = []
|
|
88
|
+
if (pkg.dsh && typeof pkg.dsh === 'object') signals.push('package.json 的 dsh 字段')
|
|
89
|
+
if (pkg.dsh?.bundle?.patch) signals.push(`dsh.bundle.patch = ${pkg.dsh.bundle.patch}`)
|
|
90
|
+
if (pkg.dsh?.plugin) signals.push('dsh.plugin.* manifest')
|
|
91
|
+
if (pkg.engines?.dsh) signals.push(`engines.dsh = ${pkg.engines.dsh}`)
|
|
92
|
+
if (existsSync(join(ROOT, 'cordis.patch.yml'))) signals.push('cordis.patch.yml')
|
|
93
|
+
if (existsSync(join(ROOT, 'agent.cordis.yml'))) signals.push('agent.cordis manifest')
|
|
94
|
+
if (signals.length) ok('DSH 兼容信号', signals.join(';'))
|
|
95
|
+
else fail('DSH 兼容信号', '缺少全部信号(dsh 字段 / cordis manifest / dsh-plugin topic)')
|
|
96
|
+
|
|
97
|
+
// ── dsh.so:README 含安装说明 ────────────────────────────────────────────
|
|
98
|
+
if (!readme) fail('README 安装说明', '没有 README.md')
|
|
99
|
+
else {
|
|
100
|
+
const installLines = readme
|
|
101
|
+
.split('\n')
|
|
102
|
+
.filter((line) => /dsh plugin --profile \S+ add /.test(line))
|
|
103
|
+
// 「公开」口径:这条安装命令不依赖内网 registry,市场用户照抄就能装
|
|
104
|
+
const publicLine = installLines.find(
|
|
105
|
+
(line) => !/registry=|\.npmrc|repository\/npm-/.test(line) && !/@\d/.test(line),
|
|
106
|
+
)
|
|
107
|
+
if (installLines.length === 0) {
|
|
108
|
+
fail('README 安装说明', 'README 里找不到 `dsh plugin --profile <p> add …`')
|
|
109
|
+
} else if (publicLine) {
|
|
110
|
+
ok('README 安装说明', `含公开可直接抄的安装命令:${short(publicLine, 80)}${installLines.length > 1 ? `(另有 ${installLines.length - 1} 条内网/带版本号的备选)` : ''}`)
|
|
111
|
+
} else {
|
|
112
|
+
warn('README 安装说明', '安装命令都指向内部 registry 或写死了版本号;公开上架需要一条不依赖内网、不写死版本的 `dsh plugin --profile web add <包名>`')
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── dsh.so:开源 license + SPDX ─────────────────────────────────────────
|
|
117
|
+
const licenseFiles = (await listFiles('.')).filter((f) => /^LICEN[CS]E(\..*)?$/i.test(f))
|
|
118
|
+
const spdx = typeof pkg.license === 'string' ? pkg.license.trim() : ''
|
|
119
|
+
const spdxOk = /^[A-Za-z0-9.+-]+$/.test(spdx) && !/^(UNLICENSED|SEE LICENSE IN .*|PROPRIETARY)$/i.test(spdx)
|
|
120
|
+
if (spdxOk && licenseFiles.length) ok('License(SPDX)', `${spdx},文件:${licenseFiles.join(', ')}`)
|
|
121
|
+
else if (!spdx && !licenseFiles.length) fail('License(SPDX)', '既没有 package.json 的 license 字段,也没有 LICENSE 文件;dsh.so 要求开源许可证 + 可识别 SPDX(UNLICENSED 不算)')
|
|
122
|
+
else if (!spdx) fail('License(SPDX)', `有 ${licenseFiles.join(', ')} 但 package.json 缺 license 字段(要写 SPDX id)`)
|
|
123
|
+
else fail('License(SPDX)', `license 字段是 "${spdx}",不是可识别的 SPDX 开源标识`)
|
|
124
|
+
|
|
125
|
+
// ── dsh.so:可扫描源码 ───────────────────────────────────────────────────
|
|
126
|
+
const sourceFiles = (await listFiles('.')).filter((f) => SRC_EXT.has(extname(f).toLowerCase()))
|
|
127
|
+
if (sourceFiles.length >= 3) ok('可扫描源码', `${sourceFiles.length} 个源码/脚本文件(如 ${sourceFiles.slice(0, 3).join(', ')}…)`)
|
|
128
|
+
else fail('可扫描源码', `只找到 ${sourceFiles.length} 个可扫描文件;docs-only / 空仓库不能提交`)
|
|
129
|
+
|
|
130
|
+
// ── dsh-market:安装来源(npm 包名) ─────────────────────────────────────
|
|
131
|
+
if (typeof pkg.name === 'string' && pkg.name) ok('npm 包名(目录 install 字段)', `${pkg.name}(目录里 npm/version 取自公开 registry)`)
|
|
132
|
+
else fail('npm 包名', 'package.json 缺 name')
|
|
133
|
+
|
|
134
|
+
// ── dsh.so:仓库地址(公开托管 + topic) ──────────────────────────────────
|
|
135
|
+
const repoUrl = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url
|
|
136
|
+
if (!repoUrl) {
|
|
137
|
+
fail('公开仓库地址', 'package.json 缺 repository(dsh.so 只收 GitHub / Gitee / GitLab,且用它同步星数/license/最后提交)')
|
|
138
|
+
} else {
|
|
139
|
+
const cleaned = String(repoUrl).replace(/^git\+/, '')
|
|
140
|
+
const host = (cleaned.match(/^https?:\/\/([^/]+)/) || [])[1] || ''
|
|
141
|
+
if (PUBLIC_HOSTS.some((h) => host === h || host.endsWith(`.${h}`))) ok('公开仓库地址', `${cleaned}(${host})`)
|
|
142
|
+
else fail('公开仓库地址', `${cleaned} 的 host 是 "${host || '未知'}";dsh.so 只认 GitHub/Gitee/GitLab`)
|
|
143
|
+
}
|
|
144
|
+
if (pkg.homepage || pkg.bugs?.url) ok('homepage / bugs', short(pkg.homepage || pkg.bugs.url))
|
|
145
|
+
else warn('homepage / bugs', '建议补 homepage / bugs(市场卡片与 issue 入口会用到)')
|
|
146
|
+
|
|
147
|
+
// ── dsh.so:topic(只能人工在仓库上加,本地无法验证) ────────────────────
|
|
148
|
+
const topics = Array.isArray(pkg.keywords) ? pkg.keywords.map((k) => String(k).toLowerCase()) : []
|
|
149
|
+
if (topics.includes('dsh-plugin') || topics.includes('dsh-pack')) ok('keywords 的 dsh-plugin', 'package.json keywords 含 dsh-plugin / dsh-pack')
|
|
150
|
+
else warn('keywords 的 dsh-plugin', 'package.json keywords 缺 dsh-plugin(同时记得在 GitHub 仓库加 topic `dsh-plugin`,dsh.so 靠它自动索引;这条只能人在仓库里加)')
|
|
151
|
+
|
|
152
|
+
// ── dsh-market:双语描述 ─────────────────────────────────────────────────
|
|
153
|
+
const desc = String(pkg.description || '')
|
|
154
|
+
// 目录条目需要 en/zh 两条描述;package.json 只有一个 description,至少要求它能当英文文案用
|
|
155
|
+
//(判定口径:连续 15 个 ASCII 字母/空格/标点,短串像 "Spring Boot" 不算一句英文)
|
|
156
|
+
if (!desc) fail('描述(en + zh)', 'package.json 缺 description')
|
|
157
|
+
else if (/[A-Za-z][A-Za-z ,.'’/()-]{15,}/.test(desc)) ok('描述(en + zh)', `package.json description 含英文句子:${short(desc, 80)}`)
|
|
158
|
+
else warn('描述(en + zh)', `description 只有中文/短英文(${short(desc, 60)});目录条目要自己写 en/zh 两条(市场按 UI 语言显示),建议把英文句也写进 README 首段`)
|
|
159
|
+
|
|
160
|
+
// ── dsh-market:README 截图(只从 GitHub 托管加载) ──────────────────────
|
|
161
|
+
const shots = readme ? (readme.match(/!\[[^\]]*\]\(([^)]+)\)/g) || []).length : 0
|
|
162
|
+
if (shots >= 2) ok('README 截图', `${shots} 张(市场卡片是轮播,仓库托管即可)`)
|
|
163
|
+
else warn('README 截图', `README 只有 ${shots} 张图;市场卡片需要 2–4 张(侧栏控制台 / 独立窗口 / 调试面板),且必须由 GitHub 托管`)
|
|
164
|
+
|
|
165
|
+
// ── 安全扫描启发式(dsh.so 阻断项:硬编码密钥 / 私钥) ───────────────────
|
|
166
|
+
const scanTargets = sourceFiles.filter((f) => !/client-codemirror\.js$/.test(f)).slice(0, 400)
|
|
167
|
+
const secretPatterns = [
|
|
168
|
+
{ name: '私钥块', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
|
|
169
|
+
{ name: 'AWS Access Key', re: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
170
|
+
{ name: 'GitHub token', re: /\bghp_[A-Za-z0-9]{36}\b/ },
|
|
171
|
+
{ name: 'OpenAI 风格 key', re: /\bsk-[A-Za-z0-9]{20,}\b/ },
|
|
172
|
+
{ name: '疑似硬编码口令', re: /\b(?:password|passwd|secret|apiKey|api_key)\s*[:=]\s*['"][^'"\s]{6,}['"]/i },
|
|
173
|
+
]
|
|
174
|
+
const hits = []
|
|
175
|
+
for (const rel of scanTargets) {
|
|
176
|
+
const text = await readText(rel)
|
|
177
|
+
if (!text) continue
|
|
178
|
+
for (const { name, re } of secretPatterns) {
|
|
179
|
+
if (re.test(text)) hits.push(`${rel} → ${name}`)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (hits.length) warn('安全扫描(硬编码密钥)', `${hits.length} 处命中,提交前逐条确认是否误报:${hits.slice(0, 5).join(';')}`)
|
|
183
|
+
else ok('安全扫描(硬编码密钥)', `${scanTargets.length} 个文件扫描,无私钥/密钥字面量命中`)
|
|
184
|
+
|
|
185
|
+
// ── 能力声明(本插件一定会带的外围能力,提交时会被人工复核) ──────────────
|
|
186
|
+
ok(
|
|
187
|
+
'能力边界(人工复核项)',
|
|
188
|
+
'插件会:启动/停止进程(含杀进程组)、读写工作区文件、读环境变量、注册 agent 工具。dsh.so 扫描会标出 fs-write/env/process,README 需写清用途与围栏(loopback 校验、pidfile 校验、路径限制)',
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// ── 可选联网核对 ─────────────────────────────────────────────────────────
|
|
192
|
+
if (NET) {
|
|
193
|
+
await checkNetwork(pkg, repoUrl)
|
|
194
|
+
} else {
|
|
195
|
+
warn('公开可达性(可选)', '跳过联网核对(--net 开启);内网环境通常不可达,可在有外网的机器上跑')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
report()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function head(url) {
|
|
202
|
+
try {
|
|
203
|
+
const res = await fetch(url, { method: 'HEAD', redirect: 'follow', signal: AbortSignal.timeout(8000) })
|
|
204
|
+
return res.ok || res.status === 405
|
|
205
|
+
} catch {
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function checkNetwork(pkg, repoUrl) {
|
|
211
|
+
const npmUrl = `https://registry.npmjs.org/${encodeURIComponent(pkg.name).replace('%2F', '/')}`
|
|
212
|
+
const npmOk = await head(npmUrl)
|
|
213
|
+
if (npmOk) ok('公开 npm 可达', `${npmUrl} 200`)
|
|
214
|
+
else fail('公开 npm 可达', `${npmUrl} 不可达:需先发布到公开 npm(只发在内部 registry 的包,市场装不到)`)
|
|
215
|
+
|
|
216
|
+
if (repoUrl) {
|
|
217
|
+
const url = String(repoUrl).replace(/^git\+/, '').replace(/\.git$/, '')
|
|
218
|
+
const repoOk = await head(url)
|
|
219
|
+
if (repoOk) ok('公开仓库可达', `${url} 200`)
|
|
220
|
+
else fail('公开仓库可达', `${url} 不可达或需要登录:dsh.so/dsh-market 都要公开可拉取`)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function report() {
|
|
225
|
+
const errors = results.filter((r) => r.level === 'error')
|
|
226
|
+
const warns = results.filter((r) => r.level === 'warn')
|
|
227
|
+
const icon = { ok: '✅', warn: '⚠️ ', error: '❌' }
|
|
228
|
+
if (AS_JSON) {
|
|
229
|
+
console.log(JSON.stringify({ ok: errors.length === 0, errors: errors.length, warnings: warns.length, results }, null, 2))
|
|
230
|
+
} else {
|
|
231
|
+
console.log('\n上架条件自检(dsh-market / dsh.so)· 规则见仓库外插件开发手册\n')
|
|
232
|
+
for (const r of results) {
|
|
233
|
+
console.log(`${icon[r.level]} ${r.id}`)
|
|
234
|
+
if (r.detail) console.log(` ${r.detail}`)
|
|
235
|
+
}
|
|
236
|
+
console.log(`\n合计:${results.length} 项 · ❌ ${errors.length} · ⚠️ ${warns.length}`)
|
|
237
|
+
if (errors.length) {
|
|
238
|
+
console.log('\n必须先解决:')
|
|
239
|
+
for (const r of errors) console.log(` ❌ ${r.id} — ${r.detail}`)
|
|
240
|
+
}
|
|
241
|
+
if (!errors.length) console.log(warns.length && !STRICT ? '\n静态条件里没有阻断项;⚠️ 需要人确认。' : '\n全部通过。')
|
|
242
|
+
}
|
|
243
|
+
const failed = errors.length > 0 || (STRICT && warns.length > 0)
|
|
244
|
+
process.exitCode = failed ? 1 : 0
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await main()
|
package/bin/yds.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// yds — YonBIP YDS local runtime CLI (dsh-ris-yds). Cross-OS node entry.
|
|
3
|
+
import { run } from '../lib/cli.js'
|
|
4
|
+
|
|
5
|
+
const argv = process.argv.slice(2)
|
|
6
|
+
try {
|
|
7
|
+
const result = await run(argv, { cwd: process.cwd() })
|
|
8
|
+
if (result !== undefined && result.text !== undefined) {
|
|
9
|
+
process.stdout.write(`${result.text}\n`)
|
|
10
|
+
}
|
|
11
|
+
// run() reports failures instead of setting process.exitCode: the same function is
|
|
12
|
+
// called in-process by the dsh tools, where a leaked exit code would hit the host.
|
|
13
|
+
if (Number.isInteger(result?.exitCode) && result.exitCode !== 0) {
|
|
14
|
+
process.exitCode = result.exitCode
|
|
15
|
+
}
|
|
16
|
+
} catch (error) {
|
|
17
|
+
process.stderr.write(`yds: ${error.message}\n`)
|
|
18
|
+
process.exitCode = 1
|
|
19
|
+
}
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# dsh-ris-yds bundle patch
|
|
2
|
+
#
|
|
3
|
+
# Tools-only plugin: no browser row, no client bundle. The insert below mounts
|
|
4
|
+
# the plugin so its ctx.tools registrations reach the agent session.
|
|
5
|
+
#
|
|
6
|
+
# Install + mount in one step (from anywhere):
|
|
7
|
+
#
|
|
8
|
+
# dsh plugin --profile web add <path-to-this-dir>
|
|
9
|
+
#
|
|
10
|
+
# The Loader specifier (`name`) MUST equal the package.json `name`.
|
|
11
|
+
- insert:
|
|
12
|
+
- id: yds
|
|
13
|
+
name: 'dsh-ris-yds'
|