@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 +65 -0
- package/bin/piperace +7 -0
- package/bin/piperace-gui +7 -0
- package/lib/index.js +76 -0
- package/package.json +44 -0
- package/scripts/install.js +154 -0
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
package/bin/piperace-gui
ADDED
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
|
+
})();
|