softpeach-cli 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/bin/softpeach.mjs +159 -0
- package/package.json +20 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { tunnel } from "cloudflared";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { randomBytes } from "crypto";
|
|
6
|
+
|
|
7
|
+
const SOFTPEACH_URL = process.env.SOFTPEACH_URL || "https://softpeach.onrender.com";
|
|
8
|
+
|
|
9
|
+
function printBanner() {
|
|
10
|
+
console.log("");
|
|
11
|
+
console.log(" \x1b[38;5;209m\x1b[1m🍑 SoftPeach\x1b[0m — Share your localhost for design review");
|
|
12
|
+
console.log("");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function printHelp() {
|
|
16
|
+
printBanner();
|
|
17
|
+
console.log(" \x1b[1mUsage:\x1b[0m");
|
|
18
|
+
console.log(" npx softpeach-cli share <port> [--room <name>]");
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log(" \x1b[1mExamples:\x1b[0m");
|
|
21
|
+
console.log(" npx softpeach-cli share 3000");
|
|
22
|
+
console.log(" npx softpeach-cli share 5173 --room my-project");
|
|
23
|
+
console.log(" npx softpeach-cli share 8080 --room sprint-review");
|
|
24
|
+
console.log("");
|
|
25
|
+
console.log(" \x1b[1mOptions:\x1b[0m");
|
|
26
|
+
console.log(" share <port> Tunnel localhost:<port> and open SoftPeach");
|
|
27
|
+
console.log(" --room <name> Use a specific room name (default: random)");
|
|
28
|
+
console.log(" --no-open Don't auto-open the browser");
|
|
29
|
+
console.log(" --help Show this help message");
|
|
30
|
+
console.log("");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function openBrowser(url) {
|
|
34
|
+
try {
|
|
35
|
+
const platform = process.platform;
|
|
36
|
+
if (platform === "darwin") execSync(`open "${url}"`);
|
|
37
|
+
else if (platform === "win32") execSync(`start "" "${url}"`);
|
|
38
|
+
else execSync(`xdg-open "${url}"`);
|
|
39
|
+
} catch {
|
|
40
|
+
// Silently fail — user can open manually
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function generateRoomId() {
|
|
45
|
+
return randomBytes(4).toString("hex");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function main() {
|
|
49
|
+
const args = process.argv.slice(2);
|
|
50
|
+
|
|
51
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
52
|
+
printHelp();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const command = args[0];
|
|
57
|
+
if (command !== "share") {
|
|
58
|
+
console.error(`\n \x1b[31mUnknown command: ${command}\x1b[0m`);
|
|
59
|
+
printHelp();
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const port = parseInt(args[1]);
|
|
64
|
+
if (!port || isNaN(port)) {
|
|
65
|
+
console.error("\n \x1b[31mPlease specify a port number.\x1b[0m");
|
|
66
|
+
console.log(" Example: npx softpeach-cli share 3000\n");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Parse options
|
|
71
|
+
let roomId = generateRoomId();
|
|
72
|
+
let autoOpen = true;
|
|
73
|
+
|
|
74
|
+
for (let i = 2; i < args.length; i++) {
|
|
75
|
+
if (args[i] === "--room" && args[i + 1]) {
|
|
76
|
+
roomId = args[++i];
|
|
77
|
+
} else if (args[i] === "--no-open") {
|
|
78
|
+
autoOpen = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
printBanner();
|
|
83
|
+
console.log(` \x1b[2mPort:\x1b[0m localhost:${port}`);
|
|
84
|
+
console.log(` \x1b[2mRoom:\x1b[0m ${roomId}`);
|
|
85
|
+
console.log("");
|
|
86
|
+
|
|
87
|
+
// Check if the port is actually running
|
|
88
|
+
try {
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
setTimeout(() => controller.abort(), 2000);
|
|
91
|
+
await fetch(`http://localhost:${port}`, { signal: controller.signal, mode: "no-cors" });
|
|
92
|
+
} catch {
|
|
93
|
+
console.log(` \x1b[33m⚠ Warning:\x1b[0m Nothing seems to be running on localhost:${port}`);
|
|
94
|
+
console.log(` \x1b[2mMake sure your dev server is running first.\x1b[0m`);
|
|
95
|
+
console.log("");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(" \x1b[36m⟳\x1b[0m Starting tunnel...");
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const { url, stop, connections } = tunnel({ "--url": `http://localhost:${port}` });
|
|
102
|
+
|
|
103
|
+
const tunnelUrl = await url;
|
|
104
|
+
|
|
105
|
+
// Wait for at least one connection to be ready
|
|
106
|
+
const firstConnection = await Promise.race([
|
|
107
|
+
connections.then(conns => conns),
|
|
108
|
+
new Promise(resolve => setTimeout(() => resolve(null), 5000))
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
console.log(` \x1b[32m✓\x1b[0m Tunnel ready: \x1b[4m${tunnelUrl}\x1b[0m`);
|
|
112
|
+
console.log("");
|
|
113
|
+
|
|
114
|
+
const roomUrl = `${SOFTPEACH_URL}/room/${roomId}?url=${encodeURIComponent(tunnelUrl)}`;
|
|
115
|
+
|
|
116
|
+
if (autoOpen) {
|
|
117
|
+
console.log(" \x1b[36m⟳\x1b[0m Opening SoftPeach in your browser...");
|
|
118
|
+
openBrowser(roomUrl);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log("");
|
|
122
|
+
console.log(" \x1b[1m\x1b[32m● Live!\x1b[0m Share this with your team:");
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log(` \x1b[4m${SOFTPEACH_URL}/room/${roomId}\x1b[0m`);
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log(` \x1b[2mRoom code: ${roomId}\x1b[0m`);
|
|
127
|
+
console.log(` \x1b[2mTunnel: ${tunnelUrl}\x1b[0m`);
|
|
128
|
+
console.log("");
|
|
129
|
+
console.log(" \x1b[2mPress Ctrl+C to stop sharing.\x1b[0m");
|
|
130
|
+
console.log("");
|
|
131
|
+
|
|
132
|
+
// Keep the process alive until interrupted
|
|
133
|
+
process.on("SIGINT", () => {
|
|
134
|
+
console.log("\n \x1b[33m■\x1b[0m Shutting down tunnel...");
|
|
135
|
+
stop();
|
|
136
|
+
console.log(" \x1b[32m✓\x1b[0m Done. Thanks for using SoftPeach!\n");
|
|
137
|
+
process.exit(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
process.on("SIGTERM", () => {
|
|
141
|
+
stop();
|
|
142
|
+
process.exit(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Keep alive
|
|
146
|
+
await new Promise(() => {});
|
|
147
|
+
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error(`\n \x1b[31m✗ Failed to start tunnel:\x1b[0m ${err.message}`);
|
|
150
|
+
console.log("");
|
|
151
|
+
console.log(" \x1b[2mMake sure cloudflared is accessible. You can install it:\x1b[0m");
|
|
152
|
+
console.log(" \x1b[2m brew install cloudflared\x1b[0m");
|
|
153
|
+
console.log(" \x1b[2m or visit: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/\x1b[0m");
|
|
154
|
+
console.log("");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "softpeach-cli",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Share your localhost with your team for design review on SoftPeach",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"softpeach-cli": "bin/softpeach.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": ["bin"],
|
|
10
|
+
"keywords": ["design-review", "collaboration", "localhost", "tunnel", "softpeach"],
|
|
11
|
+
"author": "Rashid Lansah",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/RashidLansah/softpeach-cli"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"cloudflared": "^0.5.0"
|
|
19
|
+
}
|
|
20
|
+
}
|