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