@hyperfixation/cli 0.1.1 → 0.1.3
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/dist/checklist.js +1 -1
- package/dist/cloud-steps/context.d.ts +2 -1
- package/dist/cloud-steps/context.js +2 -1
- package/dist/cloud-steps/coolify.d.ts +23 -3
- package/dist/cloud-steps/coolify.js +34 -8
- package/dist/cloud-steps/langfuse.d.ts +5 -0
- package/dist/cloud-steps/langfuse.js +36 -0
- package/dist/cloud-steps/repo.js +38 -3
- package/dist/config.d.ts +13 -1
- package/dist/config.js +37 -0
- package/dist/database.d.ts +41 -11
- package/dist/database.js +100 -16
- package/dist/doctor.d.ts +4 -3
- package/dist/doctor.js +80 -22
- package/dist/new-cloud.d.ts +11 -2
- package/dist/new-cloud.js +30 -21
- package/dist/providers/cloudflare.js +1 -0
- package/dist/providers/coolify.d.ts +15 -1
- package/dist/providers/coolify.js +4 -0
- package/dist/providers/github.js +1 -0
- package/dist/providers/http.d.ts +23 -6
- package/dist/providers/http.js +46 -8
- package/dist/providers/langfuse.js +2 -0
- package/dist/providers/sentry.js +1 -0
- package/dist/provision-database.js +2 -2
- package/dist/restore-check.js +16 -11
- package/dist/runner.d.ts +10 -3
- package/dist/runner.js +32 -10
- package/package.json +6 -6
package/dist/runner.d.ts
CHANGED
|
@@ -22,8 +22,13 @@ export interface Tunnel {
|
|
|
22
22
|
*/
|
|
23
23
|
export interface Runner {
|
|
24
24
|
exec(command: readonly string[], options?: ExecOptions): Promise<ExecResult>;
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Forwards a local port to `<remoteHost>:<remotePort>` as the far side sees it.
|
|
27
|
+
*
|
|
28
|
+
* `remoteHost` defaults to the far side's own loopback; it is an address on a network the far
|
|
29
|
+
* side can route to, which is how a container that publishes nothing is still reachable.
|
|
30
|
+
*/
|
|
31
|
+
tunnel(remotePort: number, remoteHost?: string): Promise<Tunnel>;
|
|
27
32
|
}
|
|
28
33
|
export declare class RunnerError extends Error {
|
|
29
34
|
constructor(message: string, options?: {
|
|
@@ -32,7 +37,9 @@ export declare class RunnerError extends Error {
|
|
|
32
37
|
}
|
|
33
38
|
/** The argv `exec` runs, `ssh` excluded — the array the test asserts against. */
|
|
34
39
|
export declare function sshExecArgv(host: string, command: readonly string[]): string[];
|
|
35
|
-
export declare function sshTunnelArgv(host: string, localPort: number, remotePort: number): string[];
|
|
40
|
+
export declare function sshTunnelArgv(host: string, localPort: number, remotePort: number, remoteHost?: string): string[];
|
|
41
|
+
/** Where a forward lands when the caller names no host: the far side's own loopback. */
|
|
42
|
+
export declare const TUNNEL_LOOPBACK = "127.0.0.1";
|
|
36
43
|
/** Single-quotes one word for a POSIX remote shell. */
|
|
37
44
|
export declare function shellQuote(command: readonly string[]): string;
|
|
38
45
|
export interface SshRunnerOptions {
|
package/dist/runner.js
CHANGED
|
@@ -37,17 +37,20 @@ export function sshExecArgv(host, command) {
|
|
|
37
37
|
// re-quoted for that shell; nothing else in this file ever builds a shell word.
|
|
38
38
|
return ["-T", ...SSH_OPTIONS, host, shellQuote(command)];
|
|
39
39
|
}
|
|
40
|
-
export function sshTunnelArgv(host, localPort, remotePort) {
|
|
40
|
+
export function sshTunnelArgv(host, localPort, remotePort, remoteHost = TUNNEL_LOOPBACK) {
|
|
41
41
|
assertHost(host);
|
|
42
|
+
assertTunnelHost(remoteHost);
|
|
42
43
|
return [
|
|
43
44
|
"-N",
|
|
44
45
|
"-T",
|
|
45
46
|
...SSH_OPTIONS,
|
|
46
47
|
"-L",
|
|
47
|
-
`${String(localPort)}
|
|
48
|
+
`${String(localPort)}:${remoteHost}:${String(remotePort)}`,
|
|
48
49
|
host,
|
|
49
50
|
];
|
|
50
51
|
}
|
|
52
|
+
/** Where a forward lands when the caller names no host: the far side's own loopback. */
|
|
53
|
+
export const TUNNEL_LOOPBACK = "127.0.0.1";
|
|
51
54
|
/** Single-quotes one word for a POSIX remote shell. */
|
|
52
55
|
export function shellQuote(command) {
|
|
53
56
|
return command.map((word) => `'${word.replaceAll("'", `'\\''`)}'`).join(" ");
|
|
@@ -59,9 +62,9 @@ export function createSshRunner(options) {
|
|
|
59
62
|
assertHost(options.host);
|
|
60
63
|
return {
|
|
61
64
|
exec: async (command, execOptions) => await spawnCollecting(ssh, sshExecArgv(options.host, command), execOptions),
|
|
62
|
-
tunnel: async (remotePort) => {
|
|
65
|
+
tunnel: async (remotePort, remoteHost = TUNNEL_LOOPBACK) => {
|
|
63
66
|
const localPort = await freeLocalPort();
|
|
64
|
-
const child = spawn(ssh, sshTunnelArgv(options.host, localPort, remotePort), {
|
|
67
|
+
const child = spawn(ssh, sshTunnelArgv(options.host, localPort, remotePort, remoteHost), {
|
|
65
68
|
stdio: ["ignore", "ignore", "pipe"],
|
|
66
69
|
});
|
|
67
70
|
let stderr = "";
|
|
@@ -75,7 +78,7 @@ export function createSshRunner(options) {
|
|
|
75
78
|
catch (cause) {
|
|
76
79
|
child.kill("SIGTERM");
|
|
77
80
|
await exited;
|
|
78
|
-
throw new RunnerError(`ssh -L ${String(localPort)}
|
|
81
|
+
throw new RunnerError(`ssh -L ${String(localPort)}:${remoteHost}:${String(remotePort)} never became ready` +
|
|
79
82
|
(stderr === "" ? "" : `: ${stderr.trim()}`), { cause });
|
|
80
83
|
}
|
|
81
84
|
return {
|
|
@@ -131,14 +134,20 @@ async function spawnCollecting(bin, args, options = {}) {
|
|
|
131
134
|
child.stderr?.on("data", (chunk) => {
|
|
132
135
|
stderr += chunk.toString("utf8");
|
|
133
136
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
const closed = new Promise((resolve, reject) => {
|
|
138
|
+
// A child that exits before reading its stdin (`true`, a refused ssh) makes the write fail with
|
|
139
|
+
// EPIPE; the exit code already says what happened. Any other stdin error is a real failure.
|
|
140
|
+
child.stdin?.on("error", (error) => {
|
|
141
|
+
if (error.code === "EPIPE")
|
|
142
|
+
return;
|
|
143
|
+
child.kill();
|
|
144
|
+
reject(error);
|
|
145
|
+
});
|
|
139
146
|
child.once("error", reject);
|
|
140
147
|
child.once("close", (exitCode) => resolve(exitCode));
|
|
141
148
|
});
|
|
149
|
+
child.stdin?.end(options.input ?? "");
|
|
150
|
+
const code = await closed;
|
|
142
151
|
return { code, stdout, stderr };
|
|
143
152
|
}
|
|
144
153
|
/**
|
|
@@ -192,6 +201,19 @@ async function canConnect(port) {
|
|
|
192
201
|
socket.once("timeout", () => done(false));
|
|
193
202
|
});
|
|
194
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* The far-side end of a `-L` forward, which is a bare address or hostname and nothing else.
|
|
206
|
+
*
|
|
207
|
+
* It arrives from `docker inspect` on the box rather than from the operator, and `-L` takes its
|
|
208
|
+
* three fields colon-separated, so a value carrying a colon or a space would silently become a
|
|
209
|
+
* different forward than the one asked for.
|
|
210
|
+
*/
|
|
211
|
+
function assertTunnelHost(remoteHost) {
|
|
212
|
+
if (!TUNNEL_HOST.test(remoteHost)) {
|
|
213
|
+
throw new RunnerError(`a tunnel's remote host must match ${TUNNEL_HOST.source}, got ${JSON.stringify(remoteHost)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const TUNNEL_HOST = /^[A-Za-z0-9._-]+$/;
|
|
195
217
|
function assertHost(host) {
|
|
196
218
|
if (!SSH_HOST.test(host)) {
|
|
197
219
|
throw new RunnerError(`HF_SSH_HOST must match ${SSH_HOST.source}, got ${JSON.stringify(host)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperfixation/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "The hf binary and its Turborepo generator templates",
|
|
6
6
|
"repository": {
|
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
"!dist/test-support/**"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@hyperfixation/auth": "0.1.
|
|
33
|
-
"@hyperfixation/core": "0.1.
|
|
34
|
-
"@hyperfixation/db": "0.1.
|
|
32
|
+
"@hyperfixation/auth": "0.1.3",
|
|
33
|
+
"@hyperfixation/core": "0.1.3",
|
|
34
|
+
"@hyperfixation/db": "0.1.3",
|
|
35
35
|
"giget": "3.3.1",
|
|
36
36
|
"pg": "^8.23.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@hyperfixation/eslint-config": "0.1.
|
|
40
|
-
"@hyperfixation/testing": "0.1.
|
|
39
|
+
"@hyperfixation/eslint-config": "0.1.3",
|
|
40
|
+
"@hyperfixation/testing": "0.1.3",
|
|
41
41
|
"@microsoft/api-extractor": "^7.59.1",
|
|
42
42
|
"@types/pg": "^8.23.1",
|
|
43
43
|
"eslint": "^10.10.0",
|