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.
Files changed (2) hide show
  1. package/bin/softpeach.mjs +159 -0
  2. 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
+ }