@piperace/cli 0.3.5

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/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Piperace NPM Wrapper
2
+
3
+ NPM wrapper around the Piperace Go binaries.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @piperace/cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ piperace --version
15
+ piperace http 3000 -d myapp
16
+ piperace login
17
+ piperace-gui
18
+ ```
19
+
20
+ ## How it works
21
+
22
+ On `npm install`, the `postinstall` script detects your platform/architecture and either:
23
+
24
+ 1. Copies the matching binary from `../piperace_client/dist/` (when installed inside the Piperace monorepo), or
25
+ 2. Downloads it from `https://app.piperace.com/downloads/v<VERSION>/`.
26
+
27
+ The downloaded binary is stored in `binaries/` inside the package and is launched by the `bin/piperace` Node.js wrapper.
28
+
29
+ ## Supported platforms
30
+
31
+ | OS | Arch | Binary |
32
+ |----------|-------|--------------------------------|
33
+ | macOS | arm64 | `piperace-darwin-arm64` |
34
+ | macOS | x64 | `piperace-darwin-amd64` |
35
+ | Linux | arm64 | `piperace-linux-arm64` |
36
+ | Linux | x64 | `piperace-linux-amd64` |
37
+ | Windows | x64 | `piperace-client.exe` |
38
+ | Windows | ia32 | `piperace-client-386.exe` |
39
+
40
+ ## Environment variables
41
+
42
+ - `PIPERACE_BINARY_PATH` — path to a manually downloaded binary to use instead of downloading.
43
+ - `PIPERACE_DOWNLOAD_URL` — override the base download URL (default: `https://app.piperace.com/downloads`).
44
+
45
+ ## Programmatic API
46
+
47
+ ```js
48
+ const piperace = require('@piperace/cli');
49
+
50
+ await piperace.run(['http', '3000', '-d', 'myapp'], {
51
+ env: { PIPERACE_ENV: 'production' }
52
+ });
53
+ ```
54
+
55
+ ## Publishing
56
+
57
+ ```bash
58
+ npm version patch
59
+ npm publish --access public
60
+ ```
61
+
62
+ ## macOS note
63
+
64
+ On macOS the downloaded binary may be quarantined. The install script automatically runs `xattr -cr` to remove the quarantine attribute. For a cleaner experience, distribute a code-signed and notarized binary.
65
+
package/bin/piperace ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ const { run } = require('../lib/index.js');
3
+
4
+ run(process.argv.slice(2)).catch((err) => {
5
+ console.error(err.message);
6
+ process.exit(1);
7
+ });
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ const { run } = require('../lib/index.js');
3
+
4
+ run(process.argv.slice(2), { gui: true }).catch((err) => {
5
+ console.error(err.message);
6
+ process.exit(1);
7
+ });
package/lib/index.js ADDED
@@ -0,0 +1,76 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const { spawn } = require('child_process');
5
+
6
+ const ROOT = path.join(__dirname, '..');
7
+ const BIN_DIR = path.join(ROOT, 'binaries');
8
+
9
+ const BINARY_MAP = {
10
+ cli: {
11
+ 'darwin arm64': 'piperace-darwin-arm64',
12
+ 'darwin x64': 'piperace-darwin-amd64',
13
+ 'linux arm64': 'piperace-linux-arm64',
14
+ 'linux x64': 'piperace-linux-amd64',
15
+ 'win32 x64': 'piperace-client.exe',
16
+ 'win32 ia32': 'piperace-client-386.exe'
17
+ },
18
+ gui: {
19
+ 'darwin arm64': 'piperace-gui-darwin-arm64',
20
+ 'darwin x64': 'piperace-gui-darwin-amd64',
21
+ 'win32 x64': 'piperace-gui.exe'
22
+ }
23
+ };
24
+
25
+ function platformKey() {
26
+ return `${os.platform()} ${os.arch()}`;
27
+ }
28
+
29
+ function getBinaryPath(kind = 'cli') {
30
+ const name = kind === 'gui' ? 'piperace-gui' : 'piperace';
31
+ const binaryPath = path.join(BIN_DIR, name + (os.platform() === 'win32' ? '.exe' : ''));
32
+
33
+ if (!fs.existsSync(binaryPath)) {
34
+ const expectedSource = BINARY_MAP[kind]?.[platformKey()];
35
+ throw new Error(
36
+ `Piperace ${kind} binary not found at ${binaryPath}.\n` +
37
+ `Expected source binary: ${expectedSource || 'none for ' + platformKey()}\n` +
38
+ `Try running: npm run postinstall\n` +
39
+ `Or set PIPERACE_BINARY_PATH to the downloaded binary.`
40
+ );
41
+ }
42
+
43
+ return binaryPath;
44
+ }
45
+
46
+ function run(args = [], options = {}) {
47
+ const kind = options.gui ? 'gui' : 'cli';
48
+ const binaryPath = getBinaryPath(kind);
49
+ const child = spawn(binaryPath, args, {
50
+ stdio: options.stdio || 'inherit',
51
+ env: { ...process.env, ...options.env },
52
+ cwd: options.cwd || process.cwd()
53
+ });
54
+
55
+ if (options.detach) {
56
+ child.unref();
57
+ return child;
58
+ }
59
+
60
+ return new Promise((resolve, reject) => {
61
+ child.on('error', reject);
62
+ child.on('exit', (code) => {
63
+ if (code === 0 || options.noReject) {
64
+ resolve(code);
65
+ } else {
66
+ reject(new Error(`Piperace exited with code ${code}`));
67
+ }
68
+ });
69
+ });
70
+ }
71
+
72
+ module.exports = {
73
+ getBinaryPath,
74
+ run,
75
+ platformKey
76
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@piperace/cli",
3
+ "version": "0.3.5",
4
+ "description": "Piperace — self-hosted ngrok alternative. NPM wrapper for the Go binary.",
5
+ "main": "lib/index.js",
6
+ "bin": {
7
+ "piperace": "bin/piperace",
8
+ "piperace-gui": "bin/piperace-gui"
9
+ },
10
+ "scripts": {
11
+ "postinstall": "node scripts/install.js",
12
+ "test": "node -e \"require('./lib/index.js').getBinaryPath(); console.log('ok')\""
13
+ },
14
+ "keywords": [
15
+ "piperace",
16
+ "tunnel",
17
+ "ngrok",
18
+ "localhost",
19
+ "expose",
20
+ "cli"
21
+ ],
22
+ "author": "Piperace Team",
23
+ "license": "MIT",
24
+ "homepage": "https://piperace.com",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/piperace/piperace-npm.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/piperace/piperace-npm/issues"
31
+ },
32
+ "engines": {
33
+ "node": ">=14.0.0"
34
+ },
35
+ "files": [
36
+ "bin/",
37
+ "lib/",
38
+ "scripts/",
39
+ "README.md"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const https = require('https');
6
+ const http = require('http');
7
+ const os = require('os');
8
+ const crypto = require('crypto');
9
+ const { execSync } = require('child_process');
10
+
11
+ const VERSION = require('../package.json').version;
12
+ const ROOT = path.join(__dirname, '..');
13
+ const BIN_DIR = path.join(ROOT, 'binaries');
14
+
15
+ // Map Node platform/arch to the binary names produced by the Go build.
16
+ const BINARY_MAP = {
17
+ cli: {
18
+ 'darwin arm64': 'piperace-darwin-arm64',
19
+ 'darwin x64': 'piperace-darwin-amd64',
20
+ 'linux arm64': 'piperace-linux-arm64',
21
+ 'linux x64': 'piperace-linux-amd64',
22
+ 'win32 x64': 'piperace-client.exe',
23
+ 'win32 ia32': 'piperace-client-386.exe'
24
+ },
25
+ gui: {
26
+ 'darwin arm64': 'piperace-gui-darwin-arm64',
27
+ 'darwin x64': 'piperace-gui-darwin-amd64',
28
+ 'win32 x64': 'piperace-gui.exe'
29
+ }
30
+ };
31
+
32
+ function platformKey() {
33
+ return `${os.platform()} ${os.arch()}`;
34
+ }
35
+
36
+ function binaryName(kind = 'cli') {
37
+ const map = BINARY_MAP[kind];
38
+ if (!map) return null;
39
+ return map[platformKey()] || null;
40
+ }
41
+
42
+ function finalBinaryName(kind = 'cli') {
43
+ return kind === 'gui' ? 'piperace-gui' : 'piperace';
44
+ }
45
+
46
+ function finalBinaryPath(kind = 'cli') {
47
+ const name = finalBinaryName(kind) + (os.platform() === 'win32' ? '.exe' : '');
48
+ return path.join(BIN_DIR, name);
49
+ }
50
+
51
+ function findLocalBinary(sourceName) {
52
+ // When the package is installed inside the piperace monorepo, the binaries
53
+ // live at ../piperace_client/dist/<sourceName>.
54
+ const candidates = [
55
+ path.join(ROOT, '..', 'piperace_client', 'dist', sourceName),
56
+ path.join(ROOT, '..', '..', 'piperace_client', 'dist', sourceName),
57
+ process.env.PIPERACE_BINARY_PATH
58
+ ];
59
+
60
+ for (const candidate of candidates) {
61
+ if (candidate && fs.existsSync(candidate)) {
62
+ return candidate;
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+
68
+ function downloadFile(url, dest) {
69
+ return new Promise((resolve, reject) => {
70
+ const client = url.startsWith('https:') ? https : http;
71
+ const file = fs.createWriteStream(dest);
72
+
73
+ client.get(url, (res) => {
74
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
75
+ file.destroy();
76
+ fs.unlinkSync(dest);
77
+ return downloadFile(res.headers.location, dest).then(resolve).catch(reject);
78
+ }
79
+ if (res.statusCode !== 200) {
80
+ file.destroy();
81
+ fs.unlinkSync(dest);
82
+ return reject(new Error(`HTTP ${res.statusCode} from ${url}`));
83
+ }
84
+
85
+ res.pipe(file);
86
+ file.on('finish', () => file.close(resolve));
87
+ }).on('error', (err) => {
88
+ try { fs.unlinkSync(dest); } catch {}
89
+ reject(err);
90
+ });
91
+ });
92
+ }
93
+
94
+ async function installBinary(kind = 'cli') {
95
+ const sourceName = binaryName(kind);
96
+ if (!sourceName) {
97
+ console.warn(`[piperace] No prebuilt ${kind} binary for ${platformKey()}.`);
98
+ console.warn(`[piperace] Supported platforms: ${Object.keys(BINARY_MAP[kind]).join(', ')}`);
99
+ return;
100
+ }
101
+
102
+ fs.mkdirSync(BIN_DIR, { recursive: true });
103
+ const target = finalBinaryPath(kind);
104
+
105
+ // Avoid re-downloading if binary already exists and looks valid.
106
+ if (fs.existsSync(target) && fs.statSync(target).size > 1000) {
107
+ try {
108
+ fs.chmodSync(target, 0o755);
109
+ } catch {}
110
+ console.log(`[piperace] ${kind} binary already installed.`);
111
+ return;
112
+ }
113
+
114
+ // 1. Try local binary from the monorepo dist folder.
115
+ const localPath = findLocalBinary(sourceName);
116
+ if (localPath) {
117
+ console.log(`[piperace] Installing ${kind} binary from local dist: ${localPath}`);
118
+ fs.copyFileSync(localPath, target);
119
+ fs.chmodSync(target, 0o755);
120
+ removeQuarantine(target);
121
+ return;
122
+ }
123
+
124
+ // 2. Fall back to downloading from the public CDN.
125
+ const baseUrl = process.env.PIPERACE_DOWNLOAD_URL || 'https://app.piperace.com/downloads';
126
+ const url = `${baseUrl}/v${VERSION}/${sourceName}`;
127
+ console.log(`[piperace] Downloading ${kind} binary from ${url}`);
128
+
129
+ try {
130
+ await downloadFile(url, target);
131
+ fs.chmodSync(target, 0o755);
132
+ removeQuarantine(target);
133
+ console.log(`[piperace] ${kind} binary installed successfully.`);
134
+ } catch (err) {
135
+ try { fs.unlinkSync(target); } catch {}
136
+ console.error(`[piperace] Failed to download ${kind} binary: ${err.message}`);
137
+ console.error(`[piperace] You can manually download ${sourceName} from ${baseUrl} and place it at ${target}`);
138
+ // Do not fail npm install; the launcher will print a helpful message later.
139
+ }
140
+ }
141
+
142
+ function removeQuarantine(filePath) {
143
+ if (os.platform() !== 'darwin') return;
144
+ try {
145
+ execSync(`xattr -cr "${filePath}"`, { stdio: 'ignore' });
146
+ } catch {
147
+ // Ignore — xattr may not be available, or the file may not be quarantined.
148
+ }
149
+ }
150
+
151
+ (async () => {
152
+ await installBinary('cli');
153
+ await installBinary('gui');
154
+ })();