xpose-cli 0.1.0 → 0.3.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 mt-tunnel-cli Contributors
3
+ Copyright (c) 2026 xpose Contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/bin/install.js CHANGED
@@ -4,14 +4,21 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
6
  const https = require('https');
7
+ const crypto = require('crypto');
7
8
 
8
9
  /**
9
10
  * xpose binary downloader
10
11
  * Fetches the pre-compiled Rust binary for the current platform.
11
12
  */
12
13
 
13
- const VERSION = '0.1.0';
14
- const REPO = 'user/xpose-cli'; // Placeholder
14
+ function getPackageVersion() {
15
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
16
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
17
+ return pkg.version;
18
+ }
19
+
20
+ const VERSION = getPackageVersion();
21
+ const REPO = 'vkaylee/xpose-cli';
15
22
  const BASE_URL = `https://github.com/${REPO}/releases/download/v${VERSION}`;
16
23
 
17
24
  const BIN_DIR = path.join(os.homedir(), '.xpose', 'bin');
@@ -38,42 +45,127 @@ function getReleaseName() {
38
45
  return `xpose-${target}.tar.gz`;
39
46
  }
40
47
 
48
+ function fetch(url) {
49
+ return new Promise((resolve, reject) => {
50
+ https.get(url, (res) => {
51
+ if (res.statusCode === 301 || res.statusCode === 302) {
52
+ return fetch(res.headers.location).then(resolve).catch(reject);
53
+ }
54
+ if (res.statusCode !== 200) {
55
+ return reject(new Error(`Failed to fetch ${url} (Status: ${res.statusCode})`));
56
+ }
57
+ resolve(res);
58
+ }).on('error', reject);
59
+ });
60
+ }
61
+
62
+ function getFileContent(url) {
63
+ return new Promise((resolve, reject) => {
64
+ https.get(url, (res) => {
65
+ if (res.statusCode === 301 || res.statusCode === 302) {
66
+ return getFileContent(res.headers.location).then(resolve).catch(reject);
67
+ }
68
+ let data = '';
69
+ res.on('data', hunk => data += hunk);
70
+ res.on('end', () => resolve(data.trim()));
71
+ res.on('error', reject);
72
+ });
73
+ });
74
+ }
75
+
41
76
  async function download() {
77
+ let cliProgress;
78
+ try {
79
+ cliProgress = require('cli-progress');
80
+ } catch (e) {
81
+ console.log('Progress bar library not found, skipping visual progress.');
82
+ }
83
+
42
84
  if (!fs.existsSync(BIN_DIR)) {
43
85
  fs.mkdirSync(BIN_DIR, { recursive: true });
44
86
  }
45
87
 
46
88
  const releaseName = getReleaseName();
47
89
  const url = `${BASE_URL}/${releaseName}`;
90
+ const checksumUrl = `${url}.sha256`;
48
91
  const dest = path.join(BIN_DIR, releaseName);
49
92
 
50
- console.log(`Downloading xpose binary from ${url}...`);
93
+ try {
94
+ console.log(`Verifying release for ${releaseName}...`);
51
95
 
52
- const file = fs.createWriteStream(dest);
53
-
54
- https.get(url, (response) => {
55
- if (response.statusCode !== 200) {
56
- console.warn(`Binary not yet published to GitHub Releases (${response.statusCode}).`);
57
- console.log(`Skipping auto-download. You will need to build manually: cargo build --release`);
58
- return;
96
+ let expectedChecksum;
97
+ try {
98
+ const checksumContent = await getFileContent(checksumUrl);
99
+ expectedChecksum = checksumContent.split(' ')[0];
100
+ } catch (e) {
101
+ console.warn(`⚠️ Checksum file not found at ${checksumUrl}. Skipping verification.`);
59
102
  }
60
103
 
104
+ console.log(`Downloading xpose binary...`);
105
+ const response = await fetch(url);
106
+ const totalSize = parseInt(response.headers['content-length'], 10);
107
+
108
+ const file = fs.createWriteStream(dest);
109
+ const hash = crypto.createHash('sha256');
110
+
111
+ const progressBar = cliProgress ? new cliProgress.SingleBar({
112
+ format: 'Progress |{bar}| {percentage}% | {value}/{total} bytes',
113
+ barCompleteChar: '\u2588',
114
+ barIncompleteChar: '\u2591',
115
+ hideCursor: true
116
+ }) : null;
117
+
118
+ if (progressBar) progressBar.start(totalSize, 0);
119
+
120
+ let downloadedSize = 0;
121
+ response.on('data', (chunk) => {
122
+ downloadedSize += chunk.length;
123
+ if (progressBar) progressBar.update(downloadedSize);
124
+ hash.update(chunk);
125
+ });
126
+
61
127
  response.pipe(file);
62
128
 
63
- file.on('finish', () => {
64
- file.close();
65
- console.log('Download complete.');
66
- // Note: In a full implementation, we would extract the tar.gz here.
67
- // For now, this establishes the flow.
129
+ await new Promise((resolve, reject) => {
130
+ file.on('finish', () => {
131
+ if (progressBar) progressBar.stop();
132
+ file.close();
133
+ resolve();
134
+ });
135
+ file.on('error', reject);
68
136
  });
69
- }).on('error', (err) => {
70
- console.error(`Download failed: ${err.message}`);
71
- });
137
+
138
+ if (expectedChecksum) {
139
+ const actualChecksum = hash.digest('hex');
140
+ if (actualChecksum === expectedChecksum) {
141
+ console.log('✅ Integrity verified: SHA256 matches.');
142
+ } else {
143
+ console.error('❌ Integrity check FAILED: Checksum mismatch!');
144
+ fs.unlinkSync(dest);
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ console.log(`Successfully installed to ${dest}`);
150
+ console.log('Note: Run "xpose" to start the tunnel.');
151
+
152
+ } catch (err) {
153
+ if (err.message.includes('404')) {
154
+ console.warn(`Binary not yet published to GitHub Releases.`);
155
+ console.log(`Skipping auto-download. You can build manually: cargo build --release`);
156
+ } else {
157
+ console.error(`Download failed: ${err.message}`);
158
+ }
159
+ }
72
160
  }
73
161
 
74
162
  // Only download if being installed via NPM (not in local dev)
75
- if (!process.env.XPOSE_DEV) {
76
- download();
77
- } else {
78
- console.log('XPOSE_DEV detected, skipping download.');
163
+ if (require.main === module) {
164
+ if (!process.env.XPOSE_DEV) {
165
+ download();
166
+ } else {
167
+ console.log('XPOSE_DEV detected, skipping download.');
168
+ }
79
169
  }
170
+
171
+ module.exports = { download, getReleaseName };
@@ -0,0 +1,37 @@
1
+ const assert = require('assert');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const crypto = require('crypto');
5
+ const http = require('http'); // We'll use this to mock
6
+
7
+ // Mocking dependencies would be complex without a library like proxyquire or jest.
8
+ // For a simple standalone test, we'll use environment variables and temporary paths.
9
+
10
+ const { download } = require('./install');
11
+
12
+ async function testSuccessfulDownload() {
13
+ console.log('Running testSuccessfulDownload...');
14
+ // This is a minimal test to ensure the script doesn't crash
15
+ // and correctly identifies dev environment.
16
+ process.env.XPOSE_DEV = '1';
17
+ try {
18
+ await download();
19
+ console.log('✅ testSuccessfulDownload passed (dev skip)');
20
+ } catch (e) {
21
+ console.error('❌ testSuccessfulDownload failed:', e);
22
+ process.exit(1);
23
+ }
24
+ }
25
+
26
+ // In a real scenario, we would mock https.get and fs.createWriteStream.
27
+ // Since we want to stay within standard node tools for now, we'll verify the logic structure.
28
+
29
+ async function runTests() {
30
+ await testSuccessfulDownload();
31
+ console.log('\nAll tests passed!');
32
+ }
33
+
34
+ runTests().catch(err => {
35
+ console.error(err);
36
+ process.exit(1);
37
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xpose-cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.2",
4
4
  "description": "Cloudflare Tunnel CLI for developers - Vivid TUI, auto-copy, and smart defaults.",
5
5
  "bin": {
6
6
  "xpose": "bin/cli.js"
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "https://github.com/user/xpose-cli"
14
+ "url": "https://github.com/vkaylee/xpose-cli"
15
15
  },
16
16
  "keywords": [
17
17
  "cloudflare",
@@ -24,9 +24,13 @@
24
24
  "author": "",
25
25
  "license": "MIT",
26
26
  "scripts": {
27
- "postinstall": "node bin/install.js"
27
+ "postinstall": "node bin/install.js",
28
+ "test:install": "node bin/test-install.js"
28
29
  },
29
30
  "engines": {
30
31
  "node": ">=16.0.0"
32
+ },
33
+ "dependencies": {
34
+ "cli-progress": "^3.12.0"
31
35
  }
32
- }
36
+ }