@deveco-test/deveco-code 0.1.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/LICENSE +21 -0
- package/README.md +239 -0
- package/bin/deveco +199 -0
- package/package.json +22 -0
- package/postinstall.mjs +216 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 opencode
|
|
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
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">DevEco Code</h1>
|
|
3
|
+
</p>
|
|
4
|
+
<p align="center">面向 HarmonyOS(鸿蒙)开发场景的 AI Agent 工具。</p>
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@deveco/deveco-code"><img alt="npm" src="https://img.shields.io/npm/v/@deveco/deveco-code?style=flat-square&label=npm" /></a>
|
|
7
|
+
<a href="https://gitcode.com/openharmony-sig/deveco-code/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/npm/l/@deveco/deveco-code?style=flat-square&label=license" /></a>
|
|
8
|
+
<a href="https://gitcode.com/openharmony-sig/deveco-code"><img alt="GitCode" src="https://img.shields.io/badge/GitCode-openharmony--sig%2Fdeveco--code-blue?style=flat-square" /></a>
|
|
9
|
+
</p>
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="README_EN.md">English</a> |
|
|
12
|
+
<a href="https://www.npmjs.com/package/@deveco/deveco-code">npm 包页面</a> |
|
|
13
|
+
<a href="https://gitcode.com/openharmony-sig/deveco-code">GitCode 仓库</a> |
|
|
14
|
+
<a href="https://opencode.ai">OpenCode</a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="./assets/readme/readme-screenshot.png" alt="DevEco Code" width="600">
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
***
|
|
22
|
+
|
|
23
|
+
## 快速开始
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 1. 安装
|
|
27
|
+
npm install -g @deveco/deveco-code
|
|
28
|
+
|
|
29
|
+
# 2. 启动
|
|
30
|
+
deveco
|
|
31
|
+
|
|
32
|
+
# 3. 开始对话 —— 在终端中直接描述你的鸿蒙开发需求
|
|
33
|
+
提示词示例:
|
|
34
|
+
- 解释一下代码库的架构
|
|
35
|
+
- 帮我重构login_check这个函数
|
|
36
|
+
- 帮我检查并修复语法错误
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> 如需编译构建、设备运行等能力,请先安装 [DevEco Studio](https://developer.huawei.com/consumer/cn/deveco-studio/) 并配置 `DEVECO_HOME` 环境变量。
|
|
40
|
+
|
|
41
|
+
## 简介
|
|
42
|
+
|
|
43
|
+
DevEco Code 是一款面向 HarmonyOS 开发场景的 AI Agent 工具,支持代码编写、编译构建、设备运行、文档查阅、运行时调试及 ArkTS 问题修复等能力。
|
|
44
|
+
|
|
45
|
+
DevEco Code 基于开源项目 OpenCode 扩展开发,保留了 OpenCode 的终端交互、配置体系、 Provider / MCP / Skill / Plugin 等能力,并针对鸿蒙工程增加了 DevEco Studio、Hvigor、HDC、ArkTS 检查和设备调试相关集成。
|
|
46
|
+
|
|
47
|
+
## 支持平台
|
|
48
|
+
|
|
49
|
+
- Windows x64
|
|
50
|
+
- macOS Apple Silicon
|
|
51
|
+
- macOS Intel x64
|
|
52
|
+
|
|
53
|
+
## 安装前置
|
|
54
|
+
|
|
55
|
+
DevEco Code 通过 npm 分发,安装前请先准备以下环境:
|
|
56
|
+
|
|
57
|
+
1. 安装 [Node.js](https://nodejs.org),**推荐使用 22 及更高版本**
|
|
58
|
+
2. 安装 [DevEco Studio](https://developer.huawei.com/consumer/cn/deveco-studio/),**推荐使用 6.1 及更高版本**
|
|
59
|
+
3. 配置 `DEVECO_HOME` 环境变量指向 DevEco Studio 安装目录
|
|
60
|
+
|
|
61
|
+
可先在终端验证 Node.js 环境:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
node -v
|
|
65
|
+
npm -v
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 安装与卸载
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# 安装
|
|
72
|
+
npm install -g @deveco/deveco-code
|
|
73
|
+
|
|
74
|
+
# 查看版本
|
|
75
|
+
deveco --version
|
|
76
|
+
|
|
77
|
+
# 启动
|
|
78
|
+
deveco
|
|
79
|
+
|
|
80
|
+
# 更新
|
|
81
|
+
deveco upgrade
|
|
82
|
+
|
|
83
|
+
# 卸载运行时数据
|
|
84
|
+
deveco uninstall
|
|
85
|
+
|
|
86
|
+
# 卸载 npm 全局包
|
|
87
|
+
npm uninstall -g @deveco/deveco-code
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> macOS 如果遇到全局安装权限问题,可尝试 `sudo -i npm install -g @deveco/deveco-code`。
|
|
91
|
+
|
|
92
|
+
## 登录与模型
|
|
93
|
+
|
|
94
|
+
启动 `deveco` 需华为账号登录。登录后可使用 DevEco Code 提供的免费模型通道。
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 退出登录
|
|
98
|
+
deveco auth logout
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
在 DevEco Code 中输入 `/models` 可进入模型配置界面。当前免费提供 `GLM-5.1` 模型,单账号默认每分钟 50 次请求。也可以通过 `Ctrl+A` 进入 Provider 选择界面,配置支持的第三方模型。
|
|
102
|
+
|
|
103
|
+
也可以通过 `deveco.jsonc` 配置模型:
|
|
104
|
+
|
|
105
|
+
```jsonc
|
|
106
|
+
{
|
|
107
|
+
"$schema": "https://opencode.ai/config.json",
|
|
108
|
+
"provider": {
|
|
109
|
+
"deveco": {
|
|
110
|
+
"name": "DevEco Code",
|
|
111
|
+
"models": {
|
|
112
|
+
"glm-5": {
|
|
113
|
+
"tool_call": true,
|
|
114
|
+
"limit": {
|
|
115
|
+
"context": 200000,
|
|
116
|
+
"output": 8192
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"options": {
|
|
121
|
+
"baseURL": "https://api.openbitfun.com/v1",
|
|
122
|
+
"apiKey": "{env:DEVECO_API_KEY}"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
配置文件读取优先级:
|
|
130
|
+
|
|
131
|
+
1. 项目目录下 `.deveco/deveco.jsonc`
|
|
132
|
+
2. 项目目录下 `deveco.jsonc`
|
|
133
|
+
3. 用户目录下 `.config/deveco/deveco.jsonc`
|
|
134
|
+
|
|
135
|
+
## Agent 模式
|
|
136
|
+
|
|
137
|
+
DevEco Code 面向鸿蒙开发提供以下 Agent 模式(按 `Tab` 键切换):
|
|
138
|
+
|
|
139
|
+
- `Build`:默认模式,适合工程生成、代码生成、配置修正、测试执行、推包运行和发布执行
|
|
140
|
+
- `Plan`:适合需求拆解、技术方案、发布规划、测试规划和文档生成
|
|
141
|
+
- `Goal`:适合 `spec` 定义、规范驱动、代码生成和功能验证
|
|
142
|
+
|
|
143
|
+
## 鸿蒙场景能力
|
|
144
|
+
|
|
145
|
+
DevEco Code 集成了常用鸿蒙开发工具能力:
|
|
146
|
+
|
|
147
|
+
| 工具 | 说明 |
|
|
148
|
+
| ------------------------ | -------------------- |
|
|
149
|
+
| `build_project` | 执行编译构建并导出构建产物 |
|
|
150
|
+
| `start_app` | 在模拟器或真机上运行应用 |
|
|
151
|
+
| `hdc_log` | 收集/清理设备日志/查看连接模拟器 |
|
|
152
|
+
| `check_ets_files` | ArkTS 静态语法检查 |
|
|
153
|
+
| `arkts_knowledge_search` | 鸿蒙知识搜索 |
|
|
154
|
+
| `switch_cwd` | 切换构建项目路径 |
|
|
155
|
+
|
|
156
|
+
常见场景包括:从 0 到 1 创建鸿蒙工程、增量开发页面、修复编译报错、真机调试,以及基于多模态模型的图生文界面生成。
|
|
157
|
+
|
|
158
|
+
## 扩展能力
|
|
159
|
+
|
|
160
|
+
DevEco Code 兼容 OpenCode 的 Skill、MCP 和 Plugin 扩展方式。
|
|
161
|
+
|
|
162
|
+
### Skills
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npx skills add vercel-labs/agent-skills
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
也可以把 Skill 放到 `~/.config/deveco/skills`,重启 DevEco Code 后加载。
|
|
169
|
+
|
|
170
|
+
### MCP
|
|
171
|
+
|
|
172
|
+
可在 `~/.config/deveco/deveco.jsonc` 中配置 MCP:
|
|
173
|
+
|
|
174
|
+
```jsonc
|
|
175
|
+
{
|
|
176
|
+
"$schema": "https://opencode.ai/config.json",
|
|
177
|
+
"mcp": {
|
|
178
|
+
"playwright": {
|
|
179
|
+
"type": "local",
|
|
180
|
+
"command": ["npx", "@playwright/mcp@latest"],
|
|
181
|
+
"enabled": true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Plugins
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npm install -g oh-my-opencode
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
然后在 `deveco.jsonc` 中配置插件入口:
|
|
194
|
+
|
|
195
|
+
```jsonc
|
|
196
|
+
{
|
|
197
|
+
"plugin": [
|
|
198
|
+
"node_modules/oh-my-opencode/dist/index.js"
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 从 OpenCode 迁移
|
|
204
|
+
|
|
205
|
+
如果需要从 OpenCode 迁移到 DevEco Code,请将配置文件迁移到 DevEco Code 目录。主配置文件可参考:
|
|
206
|
+
|
|
207
|
+
```powershell
|
|
208
|
+
# Windows PowerShell
|
|
209
|
+
Copy-Item -Force "{源路径}\opencode.jsonc" "~\.config\deveco\deveco.jsonc"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# macOS
|
|
214
|
+
cp {源路径}/opencode.jsonc ~/.config/deveco/deveco.jsonc
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Skills、Agents、Plugins 也可以迁移到 `~/.config/deveco` 下的对应目录;MCP 配置项可迁移到 `deveco.jsonc` 中。
|
|
218
|
+
|
|
219
|
+
## FAQ
|
|
220
|
+
|
|
221
|
+
### 这和 OpenCode 有什么关系?
|
|
222
|
+
|
|
223
|
+
DevEco Code 基于 OpenCode 扩展开发,保留其终端 UI、Provider、MCP、Skill、Plugin 和配置体系,并额外针对 HarmonyOS 开发链路加入编译构建、设备运行、日志采集、ArkTS 检查和运行时调试等能力。
|
|
224
|
+
|
|
225
|
+
## Contributing
|
|
226
|
+
|
|
227
|
+
欢迎贡献!请在提交 Pull Request 前阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
[MIT License](LICENSE)
|
|
232
|
+
|
|
233
|
+
## 基于 OpenCode 构建的声明
|
|
234
|
+
|
|
235
|
+
本项目基于开源项目 [OpenCode](https://opencode.ai) 扩展开发。DevEco Code **并非** OpenCode 团队出品,也与 OpenCode 团队无任何附属或关联关系。如有与 DevEco Code 相关的问题,请通过 [GitCode Issue](https://gitcode.com/openharmony-sig/deveco-code/issues) 反馈,而非联系 OpenCode 社区。
|
|
236
|
+
|
|
237
|
+
***
|
|
238
|
+
|
|
239
|
+
**反馈与交流** [GitCode Issue](https://gitcode.com/openharmony-sig/deveco-code/issues)
|
package/bin/deveco
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const childProcess = require("child_process")
|
|
4
|
+
const fs = require("fs")
|
|
5
|
+
const path = require("path")
|
|
6
|
+
const os = require("os")
|
|
7
|
+
|
|
8
|
+
const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"]
|
|
9
|
+
|
|
10
|
+
function run(target) {
|
|
11
|
+
const child = childProcess.spawn(target, process.argv.slice(2), {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
child.on("error", (error) => {
|
|
16
|
+
console.error(error.message)
|
|
17
|
+
process.exit(1)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const forwarders = {}
|
|
21
|
+
for (const signal of forwardedSignals) {
|
|
22
|
+
forwarders[signal] = () => {
|
|
23
|
+
try {
|
|
24
|
+
child.kill(signal)
|
|
25
|
+
} catch {
|
|
26
|
+
// The child may have already exited.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
process.on(signal, forwarders[signal])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
child.on("exit", (code, signal) => {
|
|
33
|
+
for (const forwardedSignal of forwardedSignals) {
|
|
34
|
+
process.removeListener(forwardedSignal, forwarders[forwardedSignal])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (signal) {
|
|
38
|
+
process.kill(process.pid, signal)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
process.exit(typeof code === "number" ? code : 0)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const envPath = process.env.DEVECO_BIN_PATH
|
|
47
|
+
|
|
48
|
+
const scriptPath = fs.realpathSync(__filename)
|
|
49
|
+
const scriptDir = path.dirname(scriptPath)
|
|
50
|
+
|
|
51
|
+
//
|
|
52
|
+
const cached = path.join(scriptDir, ".deveco")
|
|
53
|
+
|
|
54
|
+
const platformMap = {
|
|
55
|
+
darwin: "darwin",
|
|
56
|
+
linux: "linux",
|
|
57
|
+
win32: "windows",
|
|
58
|
+
}
|
|
59
|
+
const archMap = {
|
|
60
|
+
x64: "x64",
|
|
61
|
+
arm64: "arm64",
|
|
62
|
+
arm: "arm",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let platform = platformMap[os.platform()]
|
|
66
|
+
if (!platform) {
|
|
67
|
+
platform = os.platform()
|
|
68
|
+
}
|
|
69
|
+
let arch = archMap[os.arch()]
|
|
70
|
+
if (!arch) {
|
|
71
|
+
arch = os.arch()
|
|
72
|
+
}
|
|
73
|
+
const base = "@deveco-test/deveco-code-" + platform + "-" + arch
|
|
74
|
+
const binary = platform === "windows" ? "deveco.exe" : "deveco"
|
|
75
|
+
|
|
76
|
+
function supportsAvx2() {
|
|
77
|
+
if (arch !== "x64") return false
|
|
78
|
+
|
|
79
|
+
if (platform === "linux") {
|
|
80
|
+
try {
|
|
81
|
+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
82
|
+
} catch {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (platform === "darwin") {
|
|
88
|
+
try {
|
|
89
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
timeout: 1500,
|
|
92
|
+
})
|
|
93
|
+
if (result.status !== 0) return false
|
|
94
|
+
return (result.stdout || "").trim() === "1"
|
|
95
|
+
} catch {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (platform === "windows") {
|
|
101
|
+
const cmd =
|
|
102
|
+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
103
|
+
|
|
104
|
+
for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
105
|
+
try {
|
|
106
|
+
const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], {
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
timeout: 3000,
|
|
109
|
+
windowsHide: true,
|
|
110
|
+
})
|
|
111
|
+
if (result.status !== 0) continue
|
|
112
|
+
const out = (result.stdout || "").trim().toLowerCase()
|
|
113
|
+
if (out === "true" || out === "1") return true
|
|
114
|
+
if (out === "false" || out === "0") return false
|
|
115
|
+
} catch {
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const names = (() => {
|
|
127
|
+
const avx2 = supportsAvx2()
|
|
128
|
+
const baseline = arch === "x64" && !avx2
|
|
129
|
+
|
|
130
|
+
if (platform === "linux") {
|
|
131
|
+
const musl = (() => {
|
|
132
|
+
try {
|
|
133
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
134
|
+
} catch {
|
|
135
|
+
// ignore
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
140
|
+
const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
|
|
141
|
+
if (text.includes("musl")) return true
|
|
142
|
+
} catch {
|
|
143
|
+
// ignore
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return false
|
|
147
|
+
})()
|
|
148
|
+
|
|
149
|
+
if (musl) {
|
|
150
|
+
if (arch === "x64") {
|
|
151
|
+
if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
152
|
+
return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
153
|
+
}
|
|
154
|
+
return [`${base}-musl`, base]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (arch === "x64") {
|
|
158
|
+
if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
159
|
+
return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
160
|
+
}
|
|
161
|
+
return [base, `${base}-musl`]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (arch === "x64") {
|
|
165
|
+
if (baseline) return [`${base}-baseline`, base]
|
|
166
|
+
return [base, `${base}-baseline`]
|
|
167
|
+
}
|
|
168
|
+
return [base]
|
|
169
|
+
})()
|
|
170
|
+
|
|
171
|
+
function findBinary(startDir) {
|
|
172
|
+
let current = startDir
|
|
173
|
+
for (;;) {
|
|
174
|
+
const modules = path.join(current, "node_modules")
|
|
175
|
+
if (fs.existsSync(modules)) {
|
|
176
|
+
for (const name of names) {
|
|
177
|
+
const candidate = path.join(modules, name, "bin", binary)
|
|
178
|
+
if (fs.existsSync(candidate)) return candidate
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const parent = path.dirname(current)
|
|
182
|
+
if (parent === current) {
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
current = parent
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir))
|
|
190
|
+
if (!resolved) {
|
|
191
|
+
console.error(
|
|
192
|
+
"It seems that your package manager failed to install the right version of the deveco CLI for your platform. You can try manually installing " +
|
|
193
|
+
names.map((n) => `\"${n}\"`).join(" or ") +
|
|
194
|
+
" package",
|
|
195
|
+
)
|
|
196
|
+
process.exit(1)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
run(resolved)
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deveco-test/deveco-code",
|
|
3
|
+
"bin": {
|
|
4
|
+
"deveco": "./bin/deveco"
|
|
5
|
+
},
|
|
6
|
+
"scripts": {
|
|
7
|
+
"postinstall": "bun ./postinstall.mjs || node ./postinstall.mjs"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.0-1",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/**/*",
|
|
13
|
+
"postinstall.mjs",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"optionalDependencies": {
|
|
18
|
+
"@deveco-test/deveco-code-windows-x64": "0.1.0-1",
|
|
19
|
+
"@deveco-test/deveco-code-darwin-x64": "0.1.0-1",
|
|
20
|
+
"@deveco-test/deveco-code-darwin-arm64": "0.1.0-1"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import childProcess from "child_process"
|
|
4
|
+
import fs from "fs"
|
|
5
|
+
import os from "os"
|
|
6
|
+
import path from "path"
|
|
7
|
+
import { createRequire } from "module"
|
|
8
|
+
import { fileURLToPath } from "url"
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const require = createRequire(import.meta.url)
|
|
12
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
|
|
13
|
+
|
|
14
|
+
const platformMap = {
|
|
15
|
+
darwin: "darwin",
|
|
16
|
+
linux: "linux",
|
|
17
|
+
win32: "windows",
|
|
18
|
+
}
|
|
19
|
+
const archMap = {
|
|
20
|
+
x64: "x64",
|
|
21
|
+
arm64: "arm64",
|
|
22
|
+
arm: "arm",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const platform = platformMap[os.platform()] ?? os.platform()
|
|
26
|
+
const arch = archMap[os.arch()] ?? os.arch()
|
|
27
|
+
const base = `@deveco-test/deveco-code-${platform}-${arch}`
|
|
28
|
+
const sourceBinary = platform === "windows" ? "deveco.exe" : "deveco"
|
|
29
|
+
const targetBinary = path.join(__dirname, "bin", "deveco.exe")
|
|
30
|
+
|
|
31
|
+
function supportsAvx2() {
|
|
32
|
+
if (arch !== "x64") return false
|
|
33
|
+
|
|
34
|
+
if (platform === "linux") {
|
|
35
|
+
try {
|
|
36
|
+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
37
|
+
} catch {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (platform === "darwin") {
|
|
43
|
+
try {
|
|
44
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
timeout: 1500,
|
|
47
|
+
})
|
|
48
|
+
if (result.status !== 0) return false
|
|
49
|
+
return (result.stdout || "").trim() === "1"
|
|
50
|
+
} catch {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (platform === "windows") {
|
|
56
|
+
const command =
|
|
57
|
+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
58
|
+
|
|
59
|
+
for (const executable of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
60
|
+
try {
|
|
61
|
+
const result = childProcess.spawnSync(executable, ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
timeout: 3000,
|
|
64
|
+
windowsHide: true,
|
|
65
|
+
})
|
|
66
|
+
if (result.status !== 0) continue
|
|
67
|
+
const output = (result.stdout || "").trim().toLowerCase()
|
|
68
|
+
if (output === "true" || output === "1") return true
|
|
69
|
+
if (output === "false" || output === "0") return false
|
|
70
|
+
} catch {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isMusl() {
|
|
80
|
+
if (platform !== "linux") return false
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
84
|
+
} catch {
|
|
85
|
+
// Ignore filesystem probes that are blocked by the host.
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
90
|
+
return `${result.stdout || ""}${result.stderr || ""}`.toLowerCase().includes("musl")
|
|
91
|
+
} catch {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function packageNames() {
|
|
97
|
+
const baseline = arch === "x64" && !supportsAvx2()
|
|
98
|
+
|
|
99
|
+
if (platform === "linux") {
|
|
100
|
+
if (isMusl()) {
|
|
101
|
+
if (arch === "x64")
|
|
102
|
+
return baseline
|
|
103
|
+
? [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
104
|
+
: [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
105
|
+
return [`${base}-musl`, base]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (arch === "x64")
|
|
109
|
+
return baseline
|
|
110
|
+
? [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
111
|
+
: [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
112
|
+
return [base, `${base}-musl`]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (arch === "x64") return baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`]
|
|
116
|
+
return [base]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resolvePackageDir(name) {
|
|
120
|
+
const packageJsonPath = require.resolve(`${name}/package.json`)
|
|
121
|
+
return path.dirname(packageJsonPath)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveBinary(name) {
|
|
125
|
+
const binaryPath = path.join(resolvePackageDir(name), "bin", sourceBinary)
|
|
126
|
+
if (!fs.existsSync(binaryPath)) throw new Error(`Binary not found at ${binaryPath}`)
|
|
127
|
+
return binaryPath
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function copyDir(src, dst) {
|
|
131
|
+
if (!fs.existsSync(dst)) fs.mkdirSync(dst, { recursive: true })
|
|
132
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
133
|
+
const srcPath = path.join(src, entry.name)
|
|
134
|
+
const dstPath = path.join(dst, entry.name)
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
copyDir(srcPath, dstPath)
|
|
137
|
+
} else {
|
|
138
|
+
fs.copyFileSync(srcPath, dstPath)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function copyVendor(packageDir) {
|
|
144
|
+
const vendorSrc = path.join(packageDir, "vendor")
|
|
145
|
+
const vendorDst = path.join(__dirname, "vendor")
|
|
146
|
+
if (fs.existsSync(vendorSrc)) {
|
|
147
|
+
copyDir(vendorSrc, vendorDst)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function installPackage(name) {
|
|
152
|
+
const version = packageJson.optionalDependencies?.[name]
|
|
153
|
+
if (!version) return
|
|
154
|
+
|
|
155
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-install-"))
|
|
156
|
+
try {
|
|
157
|
+
const result = childProcess.spawnSync(
|
|
158
|
+
"npm",
|
|
159
|
+
["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${name}@${version}`],
|
|
160
|
+
{ stdio: "inherit", windowsHide: true },
|
|
161
|
+
)
|
|
162
|
+
if (result.status !== 0) return
|
|
163
|
+
const packageDir = path.join(temp, "node_modules", name)
|
|
164
|
+
copyBinary(path.join(packageDir, "bin", sourceBinary), targetBinary)
|
|
165
|
+
copyVendor(packageDir)
|
|
166
|
+
return true
|
|
167
|
+
} finally {
|
|
168
|
+
fs.rmSync(temp, { recursive: true, force: true })
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function copyBinary(source, target) {
|
|
173
|
+
if (!fs.existsSync(source)) throw new Error(`Binary not found at ${source}`)
|
|
174
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
175
|
+
if (fs.existsSync(target)) fs.unlinkSync(target)
|
|
176
|
+
try {
|
|
177
|
+
fs.linkSync(source, target)
|
|
178
|
+
} catch {
|
|
179
|
+
fs.copyFileSync(source, target)
|
|
180
|
+
}
|
|
181
|
+
fs.chmodSync(target, 0o755)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function verifyBinary() {
|
|
185
|
+
const result = childProcess.spawnSync(targetBinary, ["--version"], {
|
|
186
|
+
encoding: "utf8",
|
|
187
|
+
stdio: "ignore",
|
|
188
|
+
windowsHide: true,
|
|
189
|
+
})
|
|
190
|
+
return result.status === 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function main() {
|
|
194
|
+
for (const name of packageNames()) {
|
|
195
|
+
try {
|
|
196
|
+
copyBinary(resolveBinary(name), targetBinary)
|
|
197
|
+
copyVendor(resolvePackageDir(name))
|
|
198
|
+
if (verifyBinary()) return
|
|
199
|
+
} catch {
|
|
200
|
+
if (installPackage(name) && verifyBinary()) return
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw new Error(
|
|
205
|
+
`It seems your package manager failed to install the right opencode CLI package. Try manually installing ${packageNames()
|
|
206
|
+
.map((name) => JSON.stringify(name))
|
|
207
|
+
.join(" or ")}.`,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
main()
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error(error.message)
|
|
215
|
+
process.exit(1)
|
|
216
|
+
}
|