@itsbjoern/roost 0.1.4 → 0.1.8
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 +21 -6
- package/bin/roost-bin +0 -0
- package/index.d.ts +58 -0
- package/index.js +112 -0
- package/install.js +14 -4
- package/package.json +13 -3
- package/bin/roost +0 -35
package/README.md
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
## @itsbjoern/roost
|
|
2
2
|
|
|
3
|
-
This is the npm wrapper for the Roost CLI.
|
|
4
|
-
|
|
5
|
-
It downloads a prebuilt `roost` binary from the GitHub Releases of `itsbjoern/roost`
|
|
6
|
-
for your platform and exposes it as the `roost` command.
|
|
3
|
+
This is the npm wrapper for the Roost CLI. It downloads a prebuilt `roost` binary from GitHub Releases and exposes the `roost` command. You can also use the **JavaScript API** from Node without shelling out.
|
|
7
4
|
|
|
8
5
|
### Install
|
|
9
6
|
|
|
10
7
|
```bash
|
|
8
|
+
# Global install — use `roost` from anywhere:
|
|
11
9
|
npm install -g @itsbjoern/roost
|
|
10
|
+
|
|
11
|
+
# Local install — use via npx:
|
|
12
|
+
npm install @itsbjoern/roost
|
|
12
13
|
```
|
|
13
14
|
|
|
14
|
-
Then:
|
|
15
|
+
Then (CLI):
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
|
-
roost init
|
|
18
|
+
roost init # after global install
|
|
19
|
+
npx roost init # after local install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### JavaScript API
|
|
23
|
+
|
|
24
|
+
Use the package as a library to get cert/key **contents** (or paths) for HTTPS config. Use `getDomainCerts` for build tools—returns literal PEM strings, no file reads:
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
const { getDomainCerts } = require('@itsbjoern/roost');
|
|
28
|
+
|
|
29
|
+
const { cert, key } = await getDomainCerts('api.local', { generate: true });
|
|
30
|
+
// Pass cert and key directly to https.createServer or Vite server.https
|
|
18
31
|
```
|
|
19
32
|
|
|
33
|
+
See the [main README](https://github.com/itsbjoern/roost#javascript-api) for full API docs and examples.
|
|
34
|
+
|
package/bin/roost-bin
ADDED
|
Binary file
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ExecFileOptions } from "child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Options for execFile when running the roost binary.
|
|
4
|
+
*/
|
|
5
|
+
export interface RunRoostOptions extends ExecFileOptions {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Options for domain path helpers. Can be combined with exec options.
|
|
9
|
+
* - generate: if true, run `domain add` when the domain doesn't exist
|
|
10
|
+
* - exact: when generating, create cert for exact domain only (no wildcard)
|
|
11
|
+
* - allow: when generating, allow any TLD (bypass allowlist)
|
|
12
|
+
*/
|
|
13
|
+
export interface DomainPathOptions extends RunRoostOptions {
|
|
14
|
+
generate?: boolean;
|
|
15
|
+
exact?: boolean;
|
|
16
|
+
allow?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolved filesystem paths for a domain's certificate and private key.
|
|
20
|
+
* Returned by {@link getDomainPaths}.
|
|
21
|
+
*/
|
|
22
|
+
export interface DomainPaths {
|
|
23
|
+
cert: string;
|
|
24
|
+
key: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Literal certificate and private key contents (PEM strings).
|
|
28
|
+
* Returned by {@link getDomainCerts} for direct use with HTTPS config—no file reads needed.
|
|
29
|
+
*/
|
|
30
|
+
export interface DomainCerts {
|
|
31
|
+
cert: string;
|
|
32
|
+
key: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function runRoost(args: string[], options?: RunRoostOptions): Promise<{
|
|
35
|
+
stdout: string;
|
|
36
|
+
stderr: string;
|
|
37
|
+
}>;
|
|
38
|
+
export declare function getDomainPath(kind: "cert" | "key", domain: string, options?: DomainPathOptions): Promise<string>;
|
|
39
|
+
export declare function getDomainCertPath(domain: string, options?: DomainPathOptions): Promise<string>;
|
|
40
|
+
export declare function getDomainKeyPath(domain: string, options?: DomainPathOptions): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve both cert and key **paths** for a domain in one call.
|
|
43
|
+
*
|
|
44
|
+
* @param domain - Domain name (e.g. "api.local")
|
|
45
|
+
* @param options - Optional { generate, exact, allow } and/or exec options
|
|
46
|
+
* @returns Promise resolving to `{ cert: string, key: string }` (filesystem paths)
|
|
47
|
+
*/
|
|
48
|
+
export declare function getDomainPaths(domain: string, options?: DomainPathOptions): Promise<DomainPaths>;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve both cert and key **contents** (literal PEM strings) for a domain in one call.
|
|
51
|
+
* Build-tool friendly: pass the result directly to HTTPS config—no file reads needed.
|
|
52
|
+
* Use `generate: true` to create the domain if it doesn't exist.
|
|
53
|
+
*
|
|
54
|
+
* @param domain - Domain name (e.g. "api.local")
|
|
55
|
+
* @param options - Optional { generate, exact, allow } and/or exec options
|
|
56
|
+
* @returns Promise resolving to `{ cert: string, key: string }` (PEM file contents)
|
|
57
|
+
*/
|
|
58
|
+
export declare function getDomainCerts(domain: string, options?: DomainPathOptions): Promise<DomainCerts>;
|
package/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runRoost = runRoost;
|
|
7
|
+
exports.getDomainPath = getDomainPath;
|
|
8
|
+
exports.getDomainCertPath = getDomainCertPath;
|
|
9
|
+
exports.getDomainKeyPath = getDomainKeyPath;
|
|
10
|
+
exports.getDomainPaths = getDomainPaths;
|
|
11
|
+
exports.getDomainCerts = getDomainCerts;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const PATH_OPTION_KEYS = ["generate", "exact", "allow"];
|
|
16
|
+
function getBinaryPath() {
|
|
17
|
+
const exeName = process.platform === "win32" ? "roost.exe" : "roost";
|
|
18
|
+
const exePath = path_1.default.join(__dirname, "bin", exeName);
|
|
19
|
+
if (!fs_1.default.existsSync(exePath)) {
|
|
20
|
+
const error = new Error("roost: binary not found. Make sure @itsbjoern/roost is installed and the postinstall step completed successfully.");
|
|
21
|
+
error.code = "ENOENT";
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
return exePath;
|
|
25
|
+
}
|
|
26
|
+
function runRoost(args, options = {}) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const exePath = getBinaryPath();
|
|
29
|
+
(0, child_process_1.execFile)(exePath, args, {
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
...options,
|
|
32
|
+
}, (error, stdout, stderr) => {
|
|
33
|
+
const out = typeof stdout === "string" ? stdout : stdout?.toString() ?? "";
|
|
34
|
+
const err = typeof stderr === "string" ? stderr : stderr?.toString() ?? "";
|
|
35
|
+
if (error) {
|
|
36
|
+
error.stdout = out;
|
|
37
|
+
error.stderr = err;
|
|
38
|
+
reject(error);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
resolve({ stdout: out, stderr: err });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function domainPathArgs(kind, domain, pathOptions = {}) {
|
|
46
|
+
const args = ["domain", "path", kind, domain];
|
|
47
|
+
if (pathOptions.generate)
|
|
48
|
+
args.push("--generate");
|
|
49
|
+
if (pathOptions.exact)
|
|
50
|
+
args.push("--exact");
|
|
51
|
+
if (pathOptions.allow)
|
|
52
|
+
args.push("--allow");
|
|
53
|
+
return args;
|
|
54
|
+
}
|
|
55
|
+
async function getDomainPath(kind, domain, options = {}) {
|
|
56
|
+
if (kind !== "cert" && kind !== "key") {
|
|
57
|
+
throw new Error(`Invalid kind "${kind}". Expected "cert" or "key".`);
|
|
58
|
+
}
|
|
59
|
+
if (!domain) {
|
|
60
|
+
throw new Error("Domain is required.");
|
|
61
|
+
}
|
|
62
|
+
const pathOptions = {};
|
|
63
|
+
const execOptions = {};
|
|
64
|
+
for (const [key, value] of Object.entries(options)) {
|
|
65
|
+
if (PATH_OPTION_KEYS.includes(key)) {
|
|
66
|
+
pathOptions[key] = value;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
execOptions[key] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const args = domainPathArgs(kind, domain, pathOptions);
|
|
73
|
+
const { stdout } = await runRoost(args, execOptions);
|
|
74
|
+
return stdout.trim();
|
|
75
|
+
}
|
|
76
|
+
async function getDomainCertPath(domain, options = {}) {
|
|
77
|
+
return getDomainPath("cert", domain, options);
|
|
78
|
+
}
|
|
79
|
+
async function getDomainKeyPath(domain, options = {}) {
|
|
80
|
+
return getDomainPath("key", domain, options);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve both cert and key **paths** for a domain in one call.
|
|
84
|
+
*
|
|
85
|
+
* @param domain - Domain name (e.g. "api.local")
|
|
86
|
+
* @param options - Optional { generate, exact, allow } and/or exec options
|
|
87
|
+
* @returns Promise resolving to `{ cert: string, key: string }` (filesystem paths)
|
|
88
|
+
*/
|
|
89
|
+
async function getDomainPaths(domain, options = {}) {
|
|
90
|
+
const [cert, key] = await Promise.all([
|
|
91
|
+
getDomainCertPath(domain, options),
|
|
92
|
+
getDomainKeyPath(domain, options),
|
|
93
|
+
]);
|
|
94
|
+
return { cert, key };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve both cert and key **contents** (literal PEM strings) for a domain in one call.
|
|
98
|
+
* Build-tool friendly: pass the result directly to HTTPS config—no file reads needed.
|
|
99
|
+
* Use `generate: true` to create the domain if it doesn't exist.
|
|
100
|
+
*
|
|
101
|
+
* @param domain - Domain name (e.g. "api.local")
|
|
102
|
+
* @param options - Optional { generate, exact, allow } and/or exec options
|
|
103
|
+
* @returns Promise resolving to `{ cert: string, key: string }` (PEM file contents)
|
|
104
|
+
*/
|
|
105
|
+
async function getDomainCerts(domain, options = {}) {
|
|
106
|
+
const paths = await getDomainPaths(domain, options);
|
|
107
|
+
const [cert, key] = await Promise.all([
|
|
108
|
+
fs_1.default.promises.readFile(paths.cert, "utf8"),
|
|
109
|
+
fs_1.default.promises.readFile(paths.key, "utf8"),
|
|
110
|
+
]);
|
|
111
|
+
return { cert, key };
|
|
112
|
+
}
|
package/install.js
CHANGED
|
@@ -112,16 +112,26 @@ async function main() {
|
|
|
112
112
|
cwd: binDir,
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
const
|
|
116
|
-
const
|
|
115
|
+
const extractedName = process.platform === "win32" ? "roost.exe" : "roost";
|
|
116
|
+
const extractedPath = path.join(binDir, extractedName);
|
|
117
117
|
|
|
118
|
-
const exists = fs.existsSync(
|
|
118
|
+
const exists = fs.existsSync(extractedPath);
|
|
119
119
|
if (!exists) {
|
|
120
120
|
throw new Error(
|
|
121
|
-
`Extracted archive did not contain expected binary at ${
|
|
121
|
+
`Extracted archive did not contain expected binary at ${extractedPath}`
|
|
122
122
|
);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// On Unix, archive contains "roost" which would overwrite the Node wrapper.
|
|
126
|
+
// Rename to roost-bin so bin/roost stays as the wrapper script.
|
|
127
|
+
let binPath;
|
|
128
|
+
if (process.platform === "win32") {
|
|
129
|
+
binPath = extractedPath; // already bin/roost.exe
|
|
130
|
+
} else {
|
|
131
|
+
binPath = path.join(binDir, "roost-bin");
|
|
132
|
+
await fs.promises.rename(extractedPath, binPath);
|
|
133
|
+
}
|
|
134
|
+
|
|
125
135
|
if (process.platform !== "win32") {
|
|
126
136
|
await fs.promises.chmod(binPath, 0o755);
|
|
127
137
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itsbjoern/roost",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Roost CLI - local HTTPS reverse proxy with automatic cert management",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
5
7
|
"bin": {
|
|
6
8
|
"roost": "bin/roost"
|
|
7
9
|
},
|
|
8
10
|
"scripts": {
|
|
9
|
-
"
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"postinstall": "node install.js",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
10
14
|
},
|
|
11
15
|
"files": [
|
|
16
|
+
"index.js",
|
|
17
|
+
"index.d.ts",
|
|
12
18
|
"bin/",
|
|
13
19
|
"install.js",
|
|
14
20
|
"README.md"
|
|
@@ -33,11 +39,15 @@
|
|
|
33
39
|
},
|
|
34
40
|
"homepage": "https://github.com/itsbjoern/roost#readme",
|
|
35
41
|
"engines": {
|
|
36
|
-
"node": ">=
|
|
42
|
+
"node": ">=18"
|
|
37
43
|
},
|
|
38
44
|
"dependencies": {
|
|
39
45
|
"tar": "^7.4.3"
|
|
40
46
|
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^22.10.0",
|
|
49
|
+
"typescript": "^5.7.0"
|
|
50
|
+
},
|
|
41
51
|
"publishConfig": {
|
|
42
52
|
"access": "public"
|
|
43
53
|
}
|
package/bin/roost
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const { spawn } = require("child_process");
|
|
6
|
-
|
|
7
|
-
function run() {
|
|
8
|
-
const exeName = process.platform === "win32" ? "roost.exe" : "roost";
|
|
9
|
-
const exePath = path.join(__dirname, exeName);
|
|
10
|
-
|
|
11
|
-
if (!fs.existsSync(exePath)) {
|
|
12
|
-
console.error(
|
|
13
|
-
"roost: binary not found. Try reinstalling:\n" +
|
|
14
|
-
" npm uninstall -g @itsbjoern/roost && npm install -g @itsbjoern/roost\n" +
|
|
15
|
-
"or download a release binary / build from source:\n" +
|
|
16
|
-
" https://github.com/itsbjoern/roost#install"
|
|
17
|
-
);
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const child = spawn(exePath, process.argv.slice(2), {
|
|
22
|
-
stdio: "inherit",
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
child.on("exit", (code, signal) => {
|
|
26
|
-
if (signal) {
|
|
27
|
-
process.kill(process.pid, signal);
|
|
28
|
-
} else {
|
|
29
|
-
process.exit(code == null ? 1 : code);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
run();
|
|
35
|
-
|