deepharness 0.0.1 → 0.0.3
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 +42 -3
- package/bin/dh.js +137 -29
- package/package.json +15 -5
- package/scripts/download-binary.js +139 -0
- package/scripts/postinstall.js +149 -0
package/README.md
CHANGED
|
@@ -4,14 +4,53 @@
|
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
7
|
+
`deepharness` npm 包会在安装时自动下载对应平台的原生 `dh` 二进制文件。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g deepharness
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
安装完成后验证:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
dh --version
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 权限问题
|
|
20
|
+
|
|
21
|
+
如果 `npm install -g` 出现 `EACCES` 权限错误,推荐将 npm 全局目录改到用户主目录:
|
|
22
|
+
|
|
7
23
|
```bash
|
|
24
|
+
mkdir -p ~/.npm-global
|
|
25
|
+
npm config set prefix '~/.npm-global'
|
|
26
|
+
export PATH="$HOME/.npm-global/bin:$PATH"
|
|
27
|
+
# 将上面 export 加入 ~/.bashrc 或 ~/.zshrc
|
|
28
|
+
|
|
8
29
|
npm install -g deepharness
|
|
9
30
|
```
|
|
10
31
|
|
|
11
|
-
|
|
32
|
+
或者使用 `npx`(无需全局安装):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx deepharness --version
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 手动指定二进制路径
|
|
39
|
+
|
|
40
|
+
如果自动下载失败,或你想使用自己编译的 `dh`,可通过环境变量指定:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export DH_BINARY_PATH=/path/to/dh
|
|
44
|
+
dh --version
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 支持的平台
|
|
48
|
+
|
|
49
|
+
安装脚本会根据当前系统自动下载对应二进制:
|
|
12
50
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
51
|
+
- Linux x64 / arm64
|
|
52
|
+
- macOS x64 / arm64
|
|
53
|
+
- Windows x64
|
|
15
54
|
|
|
16
55
|
## 使用
|
|
17
56
|
|
package/bin/dh.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn, execSync } from 'child_process';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
3
|
+
import { existsSync, realpathSync, readFileSync } from 'fs';
|
|
4
4
|
import { dirname, join, resolve } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { homedir } from 'os';
|
|
@@ -8,8 +8,44 @@ import { homedir } from 'os';
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = dirname(__filename);
|
|
10
10
|
|
|
11
|
+
const SYSTEM_INSTALL_URL = 'https://github.com/WraithN/deepharness-ent-desktop';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the real path of the wrapper script, following symlinks created by
|
|
15
|
+
* `npm link` or package managers.
|
|
16
|
+
*/
|
|
17
|
+
function resolveWrapperPath() {
|
|
18
|
+
try {
|
|
19
|
+
return realpathSync(__filename);
|
|
20
|
+
} catch {
|
|
21
|
+
return __filename;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const WRAPPER_PATH = resolveWrapperPath();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check whether a candidate path resolves to this wrapper script itself.
|
|
29
|
+
* This prevents `which dh` from returning the npm-installed JS wrapper and
|
|
30
|
+
* causing an infinite subprocess loop.
|
|
31
|
+
*/
|
|
32
|
+
function isWrapperItself(candidate) {
|
|
33
|
+
try {
|
|
34
|
+
return realpathSync(candidate) === WRAPPER_PATH;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Find the project root when the npm package is installed directly inside the
|
|
42
|
+
* source repository (development / `npm link`). Returns `null` when installed
|
|
43
|
+
* from the registry.
|
|
44
|
+
*/
|
|
11
45
|
function findProjectRoot() {
|
|
12
|
-
|
|
46
|
+
const wrapperPath = resolveWrapperPath();
|
|
47
|
+
let current = dirname(wrapperPath);
|
|
48
|
+
|
|
13
49
|
while (true) {
|
|
14
50
|
if (existsSync(join(current, 'Cargo.toml')) && existsSync(join(current, 'package.json'))) {
|
|
15
51
|
return current;
|
|
@@ -22,55 +58,127 @@ function findProjectRoot() {
|
|
|
22
58
|
}
|
|
23
59
|
}
|
|
24
60
|
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Build a list of candidate paths where the `dh` binary may live.
|
|
63
|
+
*/
|
|
64
|
+
function buildSearchPaths() {
|
|
65
|
+
const paths = [];
|
|
27
66
|
|
|
67
|
+
// 1. Explicit override from environment.
|
|
68
|
+
if (process.env.DH_BINARY_PATH) {
|
|
69
|
+
paths.push(resolve(process.env.DH_BINARY_PATH));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Project-local builds when developing from source.
|
|
28
73
|
try {
|
|
29
74
|
const projectRoot = findProjectRoot();
|
|
30
75
|
if (projectRoot) {
|
|
31
|
-
|
|
32
|
-
|
|
76
|
+
paths.push(join(projectRoot, 'target', 'release', 'dh'));
|
|
77
|
+
paths.push(join(projectRoot, 'target', 'debug', 'dh'));
|
|
33
78
|
}
|
|
34
79
|
} catch {}
|
|
35
80
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'/usr/bin/dh',
|
|
81
|
+
// 3. Common user-level install locations.
|
|
82
|
+
paths.push(
|
|
39
83
|
join(homedir(), '.local', 'bin', 'dh'),
|
|
40
84
|
join(homedir(), '.cargo', 'bin', 'dh'),
|
|
41
85
|
);
|
|
42
86
|
|
|
43
|
-
|
|
44
|
-
|
|
87
|
+
// 4. System-wide install locations.
|
|
88
|
+
paths.push('/usr/local/bin/dh', '/usr/bin/dh');
|
|
89
|
+
|
|
90
|
+
return paths;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Locate the `dh` binary, or return `null` if it cannot be found.
|
|
95
|
+
*/
|
|
96
|
+
function findDhBinary() {
|
|
97
|
+
for (const p of buildSearchPaths()) {
|
|
98
|
+
if (existsSync(p) && !isWrapperItself(p)) return p;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Fallback: rely on the user's PATH. Skip the fallback when we are already
|
|
102
|
+
// inside a wrapper-spawned process to avoid infinite recursion if `which dh`
|
|
103
|
+
// points back at this wrapper.
|
|
104
|
+
if (process.env.DH_NPM_WRAPPER === '1') {
|
|
105
|
+
return null;
|
|
45
106
|
}
|
|
46
107
|
|
|
47
108
|
try {
|
|
48
109
|
const which = execSync('which dh', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
49
|
-
if (which && existsSync(which)) return which;
|
|
110
|
+
if (which && existsSync(which) && !isWrapperItself(which)) return which;
|
|
50
111
|
} catch {}
|
|
51
112
|
|
|
52
113
|
return null;
|
|
53
114
|
}
|
|
54
115
|
|
|
55
|
-
|
|
116
|
+
function readPackageVersion() {
|
|
117
|
+
try {
|
|
118
|
+
const pkgPath = join(__dirname, '..', 'package.json');
|
|
119
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
120
|
+
return pkg.version;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
56
125
|
|
|
57
|
-
|
|
126
|
+
function printInstallInstructions() {
|
|
58
127
|
console.error('Error: `dh` binary not found.');
|
|
59
128
|
console.error('');
|
|
60
|
-
console.error('
|
|
61
|
-
console.error('
|
|
62
|
-
|
|
129
|
+
console.error('The `deepharness` npm package is a thin wrapper around the native `dh` binary.');
|
|
130
|
+
console.error('Please install the binary using one of the following methods:');
|
|
131
|
+
console.error('');
|
|
132
|
+
console.error(' 1. Install DeepHarness Desktop:');
|
|
133
|
+
console.error(` ${SYSTEM_INSTALL_URL}`);
|
|
134
|
+
console.error('');
|
|
135
|
+
console.error(' 2. Build and install from source (requires Rust):');
|
|
136
|
+
console.error(' cargo build --release -p deepharness-cli');
|
|
137
|
+
console.error(' mkdir -p ~/.local/bin');
|
|
138
|
+
console.error(' cp target/release/dh ~/.local/bin/dh');
|
|
139
|
+
console.error('');
|
|
140
|
+
console.error(' 3. If the binary is already installed in a non-standard location:');
|
|
141
|
+
console.error(' export DH_BINARY_PATH=/path/to/dh');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function ensureDhBinary() {
|
|
145
|
+
const existing = findDhBinary();
|
|
146
|
+
if (existing) return existing;
|
|
147
|
+
|
|
148
|
+
// Try to download the binary from GitHub release on first use.
|
|
149
|
+
try {
|
|
150
|
+
const { downloadDhBinary } = await import('../scripts/download-binary.js');
|
|
151
|
+
const version = readPackageVersion();
|
|
152
|
+
if (!version) {
|
|
153
|
+
throw new Error('Cannot determine package version');
|
|
154
|
+
}
|
|
155
|
+
return await downloadDhBinary(version);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.error(`[deepharness] Failed to download dh binary: ${err.message}`);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function main() {
|
|
163
|
+
const dhBin = await ensureDhBinary();
|
|
164
|
+
|
|
165
|
+
if (!dhBin) {
|
|
166
|
+
printInstallInstructions();
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const args = process.argv.slice(2);
|
|
171
|
+
const proc = spawn(dhBin, args, {
|
|
172
|
+
stdio: 'inherit',
|
|
173
|
+
cwd: process.cwd(),
|
|
174
|
+
env: { ...process.env, DH_NPM_WRAPPER: '1' },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
proc.on('exit', (code) => process.exit(code ?? 1));
|
|
178
|
+
proc.on('error', (err) => {
|
|
179
|
+
console.error(`Failed to start dh from ${dhBin}:`, err.message);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
});
|
|
63
182
|
}
|
|
64
183
|
|
|
65
|
-
|
|
66
|
-
const proc = spawn(dhBin, args, {
|
|
67
|
-
stdio: 'inherit',
|
|
68
|
-
cwd: process.cwd(),
|
|
69
|
-
env: { ...process.env, DH_NPM_WRAPPER: '1' },
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
proc.on('exit', (code) => process.exit(code));
|
|
73
|
-
proc.on('error', (err) => {
|
|
74
|
-
console.error('Failed to start dh:', err.message);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
});
|
|
184
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepharness",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DeepHarness CLI - Connect to AI coding agents with Claude Code, OpenCode, and more",
|
|
6
6
|
"bin": {
|
|
7
7
|
"dh": "bin/dh.js"
|
|
8
8
|
},
|
|
9
|
-
"scripts": {
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node scripts/postinstall.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"scripts/",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
10
17
|
"keywords": [
|
|
11
18
|
"ai",
|
|
12
19
|
"coding",
|
|
@@ -20,17 +27,20 @@
|
|
|
20
27
|
],
|
|
21
28
|
"author": "DeepHarness Team",
|
|
22
29
|
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"undici": "^6.0.0"
|
|
32
|
+
},
|
|
23
33
|
"engines": {
|
|
24
34
|
"node": ">= 18"
|
|
25
35
|
},
|
|
26
36
|
"repository": {
|
|
27
37
|
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/
|
|
38
|
+
"url": "git+https://github.com/WraithN/deepharness-ent-desktop.git"
|
|
29
39
|
},
|
|
30
40
|
"bugs": {
|
|
31
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "https://github.com/WraithN/deepharness-ent-desktop/issues"
|
|
32
42
|
},
|
|
33
|
-
"homepage": "https://github.com/
|
|
43
|
+
"homepage": "https://github.com/WraithN/deepharness-ent-desktop",
|
|
34
44
|
"publishConfig": {
|
|
35
45
|
"access": "public"
|
|
36
46
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Download the native `dh` binary from the GitHub release that matches the
|
|
4
|
+
* current platform and architecture.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from 'fs';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const GITHUB_OWNER = 'WraithN';
|
|
12
|
+
const GITHUB_REPO = 'deepharness-ent-desktop';
|
|
13
|
+
const DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
14
|
+
|
|
15
|
+
const PLATFORM_ASSET_NAMES = {
|
|
16
|
+
'linux:x64': 'dh-linux-x64',
|
|
17
|
+
'linux:arm64': 'dh-linux-arm64',
|
|
18
|
+
'darwin:x64': 'dh-darwin-x64',
|
|
19
|
+
'darwin:arm64': 'dh-darwin-arm64',
|
|
20
|
+
'win32:x64': 'dh-windows-x64.exe',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function getProxyUrl() {
|
|
24
|
+
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function fetchWithProxy(url) {
|
|
28
|
+
const proxyUrl = getProxyUrl();
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timeoutId = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (proxyUrl) {
|
|
34
|
+
try {
|
|
35
|
+
const { ProxyAgent } = await import('undici');
|
|
36
|
+
return await fetch(url, {
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
dispatcher: new ProxyAgent(proxyUrl),
|
|
39
|
+
});
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// If undici/ProxyAgent fails, fall back to default fetch so users
|
|
42
|
+
// without a problematic proxy still work.
|
|
43
|
+
console.warn(`[deepharness] Proxy download failed (${err.message}), retrying without proxy.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return await fetch(url, { signal: controller.signal });
|
|
48
|
+
} finally {
|
|
49
|
+
clearTimeout(timeoutId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getPackageVersion() {
|
|
54
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
55
|
+
const packageJsonPath = join(dirname(__filename), '..', 'package.json');
|
|
56
|
+
try {
|
|
57
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
58
|
+
return pkg.version;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getAssetName() {
|
|
65
|
+
const key = `${process.platform}:${process.arch}`;
|
|
66
|
+
const assetName = PLATFORM_ASSET_NAMES[key];
|
|
67
|
+
if (!assetName) {
|
|
68
|
+
throw new Error(`Unsupported platform/architecture: ${key}. Supported platforms: ${Object.keys(PLATFORM_ASSET_NAMES).join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
return assetName;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getBinaryName() {
|
|
74
|
+
return process.platform === 'win32' ? 'dh.exe' : 'dh';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getInstallDir() {
|
|
78
|
+
return join(homedir(), '.local', 'bin');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getBinaryPath() {
|
|
82
|
+
return join(getInstallDir(), getBinaryName());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getDownloadUrl(version, assetName) {
|
|
86
|
+
return `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/dh-v${version}/${assetName}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Download the binary for the current platform and install it to ~/.local/bin.
|
|
91
|
+
* Returns the path to the installed binary.
|
|
92
|
+
*/
|
|
93
|
+
export async function downloadDhBinary(version) {
|
|
94
|
+
const assetName = getAssetName();
|
|
95
|
+
const binaryPath = getBinaryPath();
|
|
96
|
+
const downloadUrl = getDownloadUrl(version, assetName);
|
|
97
|
+
|
|
98
|
+
console.log(`[deepharness] Downloading dh ${version} for ${process.platform}-${process.arch}...`);
|
|
99
|
+
console.log(`[deepharness] URL: ${downloadUrl}`);
|
|
100
|
+
|
|
101
|
+
const response = await fetchWithProxy(downloadUrl);
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText} (${downloadUrl})`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
107
|
+
|
|
108
|
+
mkdirSync(getInstallDir(), { recursive: true });
|
|
109
|
+
writeFileSync(binaryPath, buffer);
|
|
110
|
+
|
|
111
|
+
if (process.platform !== 'win32') {
|
|
112
|
+
chmodSync(binaryPath, 0o755);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log(`[deepharness] Installed dh to ${binaryPath}`);
|
|
116
|
+
return binaryPath;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check whether the installed binary is present.
|
|
121
|
+
*/
|
|
122
|
+
export function isBinaryInstalled() {
|
|
123
|
+
return existsSync(getBinaryPath());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { getBinaryPath, getPackageVersion };
|
|
127
|
+
|
|
128
|
+
// CLI entry point for testing or manual download.
|
|
129
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
130
|
+
const version = process.argv[2] || getPackageVersion();
|
|
131
|
+
if (!version) {
|
|
132
|
+
console.error('Usage: node download-binary.js <version>');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
downloadDhBinary(version).catch((err) => {
|
|
136
|
+
console.error(err.message);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Post-install hook for the `deepharness` npm package.
|
|
4
|
+
*
|
|
5
|
+
* The npm package is only a wrapper; it needs the native `dh` binary to be
|
|
6
|
+
* present on the system. This script verifies that the binary exists and,
|
|
7
|
+
* when possible, installs it automatically by downloading from the matching
|
|
8
|
+
* GitHub release or by building from source.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, realpathSync, readFileSync } from 'fs';
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const SYSTEM_INSTALL_URL = 'https://github.com/WraithN/deepharness-ent-desktop';
|
|
20
|
+
|
|
21
|
+
function resolveWrapperPath() {
|
|
22
|
+
try {
|
|
23
|
+
return realpathSync(__filename);
|
|
24
|
+
} catch {
|
|
25
|
+
return __filename;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const WRAPPER_PATH = resolveWrapperPath();
|
|
30
|
+
|
|
31
|
+
function isWrapperItself(candidate) {
|
|
32
|
+
try {
|
|
33
|
+
return realpathSync(candidate) === WRAPPER_PATH;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function findProjectRoot() {
|
|
40
|
+
const wrapperPath = resolveWrapperPath();
|
|
41
|
+
let current = dirname(wrapperPath);
|
|
42
|
+
|
|
43
|
+
while (true) {
|
|
44
|
+
if (existsSync(join(current, 'Cargo.toml')) && existsSync(join(current, 'package.json'))) {
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
const parent = dirname(current);
|
|
48
|
+
if (parent === current) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
current = parent;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findExistingDhBinary() {
|
|
56
|
+
const candidates = [
|
|
57
|
+
join(homedir(), '.local', 'bin', 'dh'),
|
|
58
|
+
join(homedir(), '.cargo', 'bin', 'dh'),
|
|
59
|
+
'/usr/local/bin/dh',
|
|
60
|
+
'/usr/bin/dh',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
for (const p of candidates) {
|
|
64
|
+
if (existsSync(p) && !isWrapperItself(p)) return p;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const which = execSync('which dh', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
69
|
+
if (which && existsSync(which) && !isWrapperItself(which)) return which;
|
|
70
|
+
} catch {}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readPackageVersion() {
|
|
76
|
+
try {
|
|
77
|
+
const pkgPath = join(__dirname, '..', 'package.json');
|
|
78
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
79
|
+
return pkg.version;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildFromSource(projectRoot) {
|
|
86
|
+
console.log(`[deepharness] Building native dh binary from ${projectRoot}...`);
|
|
87
|
+
try {
|
|
88
|
+
execSync('cargo build --release -p deepharness-cli', {
|
|
89
|
+
cwd: projectRoot,
|
|
90
|
+
stdio: 'inherit',
|
|
91
|
+
});
|
|
92
|
+
return true;
|
|
93
|
+
} catch {
|
|
94
|
+
console.error('[deepharness] Failed to build dh from source.');
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function downloadBinary() {
|
|
100
|
+
const { downloadDhBinary } = await import('./download-binary.js');
|
|
101
|
+
const version = readPackageVersion();
|
|
102
|
+
if (!version) {
|
|
103
|
+
throw new Error('Cannot determine package version');
|
|
104
|
+
}
|
|
105
|
+
return downloadDhBinary(version);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function main() {
|
|
109
|
+
// If a usable binary already exists, nothing more to do.
|
|
110
|
+
const existing = findExistingDhBinary();
|
|
111
|
+
if (existing) {
|
|
112
|
+
console.log(`[deepharness] Found dh binary at ${existing}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const projectRoot = findProjectRoot();
|
|
117
|
+
|
|
118
|
+
// Prefer building from source when installed inside the repository.
|
|
119
|
+
if (projectRoot) {
|
|
120
|
+
console.log('[deepharness] dh binary not found; attempting to build from source...');
|
|
121
|
+
const built = buildFromSource(projectRoot);
|
|
122
|
+
if (built) {
|
|
123
|
+
const releaseBinary = join(projectRoot, 'target', 'release', 'dh');
|
|
124
|
+
if (existsSync(releaseBinary)) {
|
|
125
|
+
console.log(`[deepharness] Built dh binary at ${releaseBinary}`);
|
|
126
|
+
console.log('[deepharness] Add it to your PATH to use the `dh` command globally:');
|
|
127
|
+
console.log(` export PATH="${join(projectRoot, 'target', 'release')}:$PATH"`);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Otherwise try to download the matching release binary.
|
|
134
|
+
try {
|
|
135
|
+
await downloadBinary();
|
|
136
|
+
return;
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.warn(`[deepharness] Could not download binary: ${err.message}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.warn('[deepharness] The native `dh` binary is not installed.');
|
|
142
|
+
console.warn('[deepharness] The `dh` command will not work until it is available.');
|
|
143
|
+
console.warn(`[deepharness] Install DeepHarness Desktop from: ${SYSTEM_INSTALL_URL}`);
|
|
144
|
+
console.warn('[deepharness] Or build from source: cargo build --release -p deepharness-cli');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch((err) => {
|
|
148
|
+
console.warn(`[deepharness] Post-install hook failed: ${err.message}`);
|
|
149
|
+
});
|