htn-tunnel 0.2.0 → 0.2.2
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/bin/htn-tunnel.js +8 -44
- package/lib/install.js +60 -65
- package/package.json +2 -17
package/bin/htn-tunnel.js
CHANGED
|
@@ -1,42 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Platform detection shim — finds the correct prebuilt binary from
|
|
4
|
-
// @htn-tunnel/<platform> optional dependency and executes it.
|
|
5
|
-
|
|
6
|
-
const { execFileSync } = require("child_process");
|
|
2
|
+
// Shim: finds and executes the htn-tunnel binary downloaded by postinstall.
|
|
7
3
|
const path = require("path");
|
|
8
4
|
const os = require("os");
|
|
9
5
|
const fs = require("fs");
|
|
10
6
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
"darwin:x64": "@htn-tunnel/darwin-x64",
|
|
14
|
-
"linux:arm64": "@htn-tunnel/linux-arm64",
|
|
15
|
-
"linux:x64": "@htn-tunnel/linux-x64",
|
|
16
|
-
"win32:arm64": "@htn-tunnel/win32-arm64",
|
|
17
|
-
"win32:x64": "@htn-tunnel/win32-x64",
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const platform = os.platform();
|
|
21
|
-
const arch = os.arch();
|
|
22
|
-
const key = `${platform}:${arch}`;
|
|
23
|
-
const pkg = PLATFORMS[key];
|
|
24
|
-
const ext = platform === "win32" ? ".exe" : "";
|
|
25
|
-
|
|
26
|
-
if (!pkg) {
|
|
27
|
-
console.error(`htn-tunnel: unsupported platform ${key}`);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Try 1: resolve from optionalDependencies package
|
|
32
|
-
let binPath;
|
|
33
|
-
try {
|
|
34
|
-
const pkgDir = path.dirname(require.resolve(`${pkg}/package.json`));
|
|
35
|
-
binPath = path.join(pkgDir, "bin", `htn-tunnel${ext}`);
|
|
36
|
-
} catch {
|
|
37
|
-
// Try 2: postinstall may have downloaded binary to our own bin/
|
|
38
|
-
binPath = path.join(__dirname, `htn-tunnel${ext}`);
|
|
39
|
-
}
|
|
7
|
+
const ext = os.platform() === "win32" ? ".exe" : "";
|
|
8
|
+
const binPath = path.join(__dirname, `htn-tunnel${ext}`);
|
|
40
9
|
|
|
41
10
|
if (!fs.existsSync(binPath)) {
|
|
42
11
|
console.error("htn-tunnel: binary not found. Try reinstalling:");
|
|
@@ -44,13 +13,8 @@ if (!fs.existsSync(binPath)) {
|
|
|
44
13
|
process.exit(1);
|
|
45
14
|
}
|
|
46
15
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
process.exit(result.status ?? 1);
|
|
53
|
-
} catch (e) {
|
|
54
|
-
console.error("htn-tunnel: failed to execute binary:", e.message);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
16
|
+
const result = require("child_process").spawnSync(binPath, process.argv.slice(2), {
|
|
17
|
+
stdio: "inherit",
|
|
18
|
+
windowsHide: false,
|
|
19
|
+
});
|
|
20
|
+
process.exit(result.status ?? 1);
|
package/lib/install.js
CHANGED
|
@@ -1,63 +1,50 @@
|
|
|
1
|
-
// Postinstall
|
|
2
|
-
// when optionalDependencies are disabled or unavailable.
|
|
3
|
-
|
|
1
|
+
// Postinstall: download htn-tunnel binary from GitHub Releases.
|
|
4
2
|
const fs = require("fs");
|
|
5
3
|
const path = require("path");
|
|
6
4
|
const os = require("os");
|
|
7
5
|
const https = require("https");
|
|
6
|
+
const { execSync } = require("child_process");
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"linux:arm64": "@htn-tunnel/linux-arm64",
|
|
13
|
-
"linux:x64": "@htn-tunnel/linux-x64",
|
|
14
|
-
"win32:arm64": "@htn-tunnel/win32-arm64",
|
|
15
|
-
"win32:x64": "@htn-tunnel/win32-x64",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const platform = os.platform();
|
|
19
|
-
const arch = os.arch();
|
|
20
|
-
const key = `${platform}:${arch}`;
|
|
21
|
-
const pkg = PLATFORMS[key];
|
|
22
|
-
|
|
23
|
-
// Check if platform package already installed via optionalDependencies
|
|
24
|
-
if (pkg) {
|
|
25
|
-
try {
|
|
26
|
-
require.resolve(`${pkg}/package.json`);
|
|
27
|
-
process.exit(0); // Already installed, nothing to do
|
|
28
|
-
} catch {
|
|
29
|
-
// Not installed — fall through to download
|
|
30
|
-
}
|
|
31
|
-
}
|
|
8
|
+
// Binary version from GitHub Releases (may differ from npm package version).
|
|
9
|
+
const VERSION = "0.2.0";
|
|
10
|
+
const REPO = "nhh0718/htn-tunnel";
|
|
32
11
|
|
|
33
|
-
const
|
|
34
|
-
const
|
|
12
|
+
const platform = os.platform(); // darwin, linux, win32
|
|
13
|
+
const arch = os.arch(); // x64, arm64
|
|
35
14
|
const goOS = platform === "win32" ? "windows" : platform;
|
|
15
|
+
const goArch = arch === "x64" ? "amd64" : arch;
|
|
36
16
|
const ext = platform === "win32" ? ".exe" : "";
|
|
17
|
+
const archiveExt = platform === "win32" ? ".zip" : ".tar.gz";
|
|
18
|
+
|
|
37
19
|
const binDir = path.join(__dirname, "..", "bin");
|
|
38
20
|
const binPath = path.join(binDir, `htn-tunnel${ext}`);
|
|
39
21
|
|
|
40
|
-
//
|
|
22
|
+
// Skip if binary already exists and is correct version
|
|
41
23
|
if (fs.existsSync(binPath) && fs.statSync(binPath).size > 0) {
|
|
42
|
-
|
|
24
|
+
try {
|
|
25
|
+
const out = execSync(`"${binPath}" --version`, { encoding: "utf8", timeout: 5000 });
|
|
26
|
+
if (out.includes(VERSION)) {
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
} catch { /* re-download */ }
|
|
43
30
|
}
|
|
44
31
|
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const archiveExt = platform === "win32" ? ".zip" : ".tar.gz";
|
|
48
|
-
const url = `${baseUrl}/${archiveName}${archiveExt}`;
|
|
32
|
+
const archiveName = `htn-tunnel_${VERSION}_${goOS}_${goArch}${archiveExt}`;
|
|
33
|
+
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
|
49
34
|
|
|
50
|
-
console.log(`htn-tunnel: downloading v${
|
|
35
|
+
console.log(`htn-tunnel: downloading v${VERSION} for ${platform}/${arch}...`);
|
|
36
|
+
console.log(` ${url}`);
|
|
51
37
|
|
|
52
|
-
function follow(url, cb) {
|
|
53
|
-
|
|
38
|
+
function follow(url, cb, redirects) {
|
|
39
|
+
if (redirects > 5) { console.error("htn-tunnel: too many redirects"); process.exit(1); }
|
|
40
|
+
https.get(url, { headers: { "User-Agent": "htn-tunnel-npm" } }, (res) => {
|
|
54
41
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
55
|
-
return follow(res.headers.location, cb);
|
|
42
|
+
return follow(res.headers.location, cb, redirects + 1);
|
|
56
43
|
}
|
|
57
44
|
cb(res);
|
|
58
45
|
}).on("error", (err) => {
|
|
59
46
|
console.error("htn-tunnel: download failed:", err.message);
|
|
60
|
-
|
|
47
|
+
printManual();
|
|
61
48
|
process.exit(1);
|
|
62
49
|
});
|
|
63
50
|
}
|
|
@@ -67,7 +54,7 @@ fs.mkdirSync(binDir, { recursive: true });
|
|
|
67
54
|
follow(url, (res) => {
|
|
68
55
|
if (res.statusCode !== 200) {
|
|
69
56
|
console.error(`htn-tunnel: download failed (HTTP ${res.statusCode})`);
|
|
70
|
-
|
|
57
|
+
printManual();
|
|
71
58
|
process.exit(1);
|
|
72
59
|
}
|
|
73
60
|
|
|
@@ -75,36 +62,34 @@ follow(url, (res) => {
|
|
|
75
62
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
76
63
|
res.on("end", () => {
|
|
77
64
|
const data = Buffer.concat(chunks);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
try {
|
|
66
|
+
if (archiveExt === ".zip") {
|
|
67
|
+
extractZip(data, `htn-tunnel${ext}`, binPath);
|
|
68
|
+
} else {
|
|
69
|
+
extractTarGz(data, `htn-tunnel${ext}`, binPath);
|
|
70
|
+
}
|
|
71
|
+
fs.chmodSync(binPath, 0o755);
|
|
72
|
+
console.log("htn-tunnel: installed successfully!");
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error("htn-tunnel: extract failed:", err.message);
|
|
75
|
+
printManual();
|
|
76
|
+
process.exit(1);
|
|
85
77
|
}
|
|
86
|
-
|
|
87
|
-
fs.chmodSync(binPath, 0o755);
|
|
88
|
-
console.log("htn-tunnel: installed successfully");
|
|
89
78
|
});
|
|
90
|
-
});
|
|
79
|
+
}, 0);
|
|
91
80
|
|
|
92
|
-
function
|
|
81
|
+
function extractTarGz(gzData, filename, dest) {
|
|
93
82
|
const zlib = require("zlib");
|
|
94
83
|
const tarData = zlib.gunzipSync(gzData);
|
|
95
|
-
|
|
96
|
-
// Simple tar parser — find file entry matching filename
|
|
97
84
|
let offset = 0;
|
|
98
85
|
while (offset < tarData.length) {
|
|
99
86
|
const header = tarData.subarray(offset, offset + 512);
|
|
100
87
|
if (header[0] === 0) break;
|
|
101
|
-
|
|
102
88
|
const name = header.toString("utf8", 0, 100).replace(/\0/g, "");
|
|
103
89
|
const sizeStr = header.toString("utf8", 124, 136).replace(/\0/g, "").trim();
|
|
104
90
|
const size = parseInt(sizeStr, 8) || 0;
|
|
105
91
|
offset += 512;
|
|
106
|
-
|
|
107
|
-
if (name.endsWith(filename)) {
|
|
92
|
+
if (name.endsWith(filename) || name === filename) {
|
|
108
93
|
fs.writeFileSync(dest, tarData.subarray(offset, offset + size));
|
|
109
94
|
return;
|
|
110
95
|
}
|
|
@@ -113,23 +98,33 @@ function extractTarGzBinary(gzData, filename, dest) {
|
|
|
113
98
|
throw new Error(`${filename} not found in archive`);
|
|
114
99
|
}
|
|
115
100
|
|
|
116
|
-
function
|
|
117
|
-
// Simple zip parser — find local file header for the binary
|
|
101
|
+
function extractZip(zipData, filename, dest) {
|
|
118
102
|
let offset = 0;
|
|
119
103
|
while (offset < zipData.length - 4) {
|
|
120
|
-
if (zipData.readUInt32LE(offset) !== 0x04034b50) break;
|
|
121
|
-
|
|
104
|
+
if (zipData.readUInt32LE(offset) !== 0x04034b50) break;
|
|
105
|
+
const compMethod = zipData.readUInt16LE(offset + 8);
|
|
106
|
+
const compSize = zipData.readUInt32LE(offset + 18);
|
|
122
107
|
const nameLen = zipData.readUInt16LE(offset + 26);
|
|
123
108
|
const extraLen = zipData.readUInt16LE(offset + 28);
|
|
124
|
-
const compSize = zipData.readUInt32LE(offset + 18);
|
|
125
109
|
const name = zipData.toString("utf8", offset + 30, offset + 30 + nameLen);
|
|
126
|
-
|
|
127
110
|
const dataStart = offset + 30 + nameLen + extraLen;
|
|
128
|
-
if (name.endsWith(filename)) {
|
|
129
|
-
|
|
111
|
+
if (name.endsWith(filename) || name === filename) {
|
|
112
|
+
if (compMethod === 0) {
|
|
113
|
+
fs.writeFileSync(dest, zipData.subarray(dataStart, dataStart + compSize));
|
|
114
|
+
} else {
|
|
115
|
+
const zlib = require("zlib");
|
|
116
|
+
const raw = zipData.subarray(dataStart, dataStart + compSize);
|
|
117
|
+
fs.writeFileSync(dest, zlib.inflateRawSync(raw));
|
|
118
|
+
}
|
|
130
119
|
return;
|
|
131
120
|
}
|
|
132
121
|
offset = dataStart + compSize;
|
|
133
122
|
}
|
|
134
123
|
throw new Error(`${filename} not found in archive`);
|
|
135
124
|
}
|
|
125
|
+
|
|
126
|
+
function printManual() {
|
|
127
|
+
console.error("\nInstall manually:");
|
|
128
|
+
console.error(` https://github.com/${REPO}/releases/tag/v${VERSION}`);
|
|
129
|
+
console.error("\nOr use: go install github.com/nhh0718/htn-tunnel/cmd/htn-tunnel@latest\n");
|
|
130
|
+
}
|
package/package.json
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htn-tunnel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Self-hosted tunnel client — expose localhost to the internet",
|
|
5
5
|
"bin": {
|
|
6
6
|
"htn-tunnel": "./bin/htn-tunnel.js"
|
|
7
7
|
},
|
|
8
|
-
"optionalDependencies": {
|
|
9
|
-
"@htn-tunnel/darwin-arm64": "0.1.1",
|
|
10
|
-
"@htn-tunnel/darwin-x64": "0.1.1",
|
|
11
|
-
"@htn-tunnel/linux-arm64": "0.1.1",
|
|
12
|
-
"@htn-tunnel/linux-x64": "0.1.1",
|
|
13
|
-
"@htn-tunnel/win32-arm64": "0.1.1",
|
|
14
|
-
"@htn-tunnel/win32-x64": "0.1.1"
|
|
15
|
-
},
|
|
16
8
|
"scripts": {
|
|
17
9
|
"postinstall": "node lib/install.js"
|
|
18
10
|
},
|
|
19
|
-
"keywords": [
|
|
20
|
-
"tunnel",
|
|
21
|
-
"ngrok",
|
|
22
|
-
"localhost",
|
|
23
|
-
"expose",
|
|
24
|
-
"dev",
|
|
25
|
-
"htn"
|
|
26
|
-
],
|
|
11
|
+
"keywords": ["tunnel", "ngrok", "localhost", "expose", "dev", "htn"],
|
|
27
12
|
"license": "MIT",
|
|
28
13
|
"repository": {
|
|
29
14
|
"type": "git",
|