happy2 0.0.16 → 0.0.17
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/Dockerfile +30 -0
- package/README.md +170 -0
- package/deploy/config.yaml +56 -0
- package/deploy/happy2-server.yaml +140 -0
- package/deploy/happy2-web.yaml +87 -0
- package/package.json +3 -2
- package/packages/happy2-server/README.md +70 -15
- package/packages/happy2-server/dist/backend.d.ts +12 -0
- package/packages/happy2-server/dist/backend.d.ts.map +1 -0
- package/packages/happy2-server/dist/backend.js +48 -0
- package/packages/happy2-server/dist/backend.js.map +1 -0
- package/packages/happy2-server/dist/cli.js +6 -25
- package/packages/happy2-server/dist/cli.js.map +1 -1
- package/packages/happy2-server/dist/index.d.ts +2 -0
- package/packages/happy2-server/dist/index.d.ts.map +1 -1
- package/packages/happy2-server/dist/index.js +2 -0
- package/packages/happy2-server/dist/index.js.map +1 -1
- package/packages/happy2-server/dist/modules/agents/daemon.d.ts +4 -0
- package/packages/happy2-server/dist/modules/agents/daemon.d.ts.map +1 -1
- package/packages/happy2-server/dist/modules/agents/daemon.js +40 -0
- package/packages/happy2-server/dist/modules/agents/daemon.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/defaults.d.ts +4 -4
- package/packages/happy2-server/dist/modules/config/defaults.d.ts.map +1 -1
- package/packages/happy2-server/dist/modules/config/defaults.js +8 -8
- package/packages/happy2-server/dist/modules/config/defaults.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/loader.d.ts +2 -2
- package/packages/happy2-server/dist/modules/config/loader.d.ts.map +1 -1
- package/packages/happy2-server/dist/modules/config/loader.js +83 -73
- package/packages/happy2-server/dist/modules/config/loader.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/loader.test.js +35 -0
- package/packages/happy2-server/dist/modules/config/loader.test.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/runtime.d.ts +1 -1
- package/packages/happy2-server/dist/modules/config/runtime.d.ts.map +1 -1
- package/packages/happy2-server/dist/modules/config/runtime.js +17 -4
- package/packages/happy2-server/dist/modules/config/runtime.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/runtime.test.d.ts +2 -0
- package/packages/happy2-server/dist/modules/config/runtime.test.d.ts.map +1 -0
- package/packages/happy2-server/dist/modules/config/runtime.test.js +99 -0
- package/packages/happy2-server/dist/modules/config/runtime.test.js.map +1 -0
- package/packages/happy2-server/dist/runner.js +29 -12
- package/packages/happy2-server/dist/runner.js.map +1 -1
- package/packages/happy2-server/dist/runnerCommand.d.ts +21 -0
- package/packages/happy2-server/dist/runnerCommand.d.ts.map +1 -0
- package/packages/happy2-server/dist/runnerCommand.js +97 -0
- package/packages/happy2-server/dist/runnerCommand.js.map +1 -0
- package/packages/happy2-server/dist/runnerCommand.test.d.ts +2 -0
- package/packages/happy2-server/dist/runnerCommand.test.d.ts.map +1 -0
- package/packages/happy2-server/dist/runnerCommand.test.js +59 -0
- package/packages/happy2-server/dist/runnerCommand.test.js.map +1 -0
- package/packages/happy2-server/dist/standalone.d.ts +2 -5
- package/packages/happy2-server/dist/standalone.d.ts.map +1 -1
- package/packages/happy2-server/dist/standalone.js +14 -65
- package/packages/happy2-server/dist/standalone.js.map +1 -1
- package/packages/happy2-server/dist/web/assets/index-B51jYSrY.css +2 -0
- package/packages/happy2-server/dist/web/assets/index-szUjvBO1.js +39 -0
- package/packages/happy2-server/dist/web/index.html +2 -2
- package/packages/happy2-server/dist/web.d.ts +12 -0
- package/packages/happy2-server/dist/web.d.ts.map +1 -0
- package/packages/happy2-server/dist/web.js +89 -0
- package/packages/happy2-server/dist/web.js.map +1 -0
- package/packages/happy2-server/happy2.example.toml +3 -5
- package/packages/happy2-server/Dockerfile +0 -18
- package/packages/happy2-server/dist/web/assets/index-BxtJtZpt.js +0 -39
- package/packages/happy2-server/dist/web/assets/index-m8C1tSYV.css +0 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs } from "node:util";
|
|
3
2
|
import { daemonStart, daemonStop, daemonUsage, parseDaemonCommand } from "./daemon.js";
|
|
4
3
|
import { isNpxInvocation, parseSystemServiceCommand, systemServiceStart, systemServiceStop, systemServiceUsage, } from "./systemService.js";
|
|
4
|
+
import { happy2Usage, parseHappy2Command } from "./runnerCommand.js";
|
|
5
5
|
try {
|
|
6
6
|
const arguments_ = process.argv.slice(2);
|
|
7
7
|
if (arguments_[0] === "daemon") {
|
|
@@ -42,17 +42,34 @@ try {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
const command = parseHappy2Command(arguments_);
|
|
46
|
+
if (command.kind === "help") {
|
|
47
|
+
console.log(happy2Usage());
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
if (command.kind === "invalid") {
|
|
51
|
+
console.error(`${command.message}\n\n${happy2Usage()}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
let running;
|
|
55
|
+
if (command.kind === "web") {
|
|
56
|
+
const { startWebHappy2 } = await import("./web.js");
|
|
57
|
+
running = await startWebHappy2(command);
|
|
58
|
+
console.log(`Happy (2) web is running at ${running.url} and proxying /v0 to ${command.backendUrl}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const { loadRuntimeConfig } = await import("./modules/config/runtime.js");
|
|
62
|
+
const { config } = await loadRuntimeConfig(command.configPath);
|
|
63
|
+
if (command.kind === "backend") {
|
|
64
|
+
const { startBackendHappy2 } = await import("./backend.js");
|
|
65
|
+
running = await startBackendHappy2(config);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const { startStandaloneHappy2 } = await import("./standalone.js");
|
|
69
|
+
running = await startStandaloneHappy2(config);
|
|
70
|
+
}
|
|
71
|
+
console.log(`Happy (2) ${command.kind} is running at ${running.url}`);
|
|
72
|
+
}
|
|
56
73
|
let stopping = false;
|
|
57
74
|
const stop = () => {
|
|
58
75
|
if (stopping)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../sources/runner.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../sources/runner.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EACH,eAAe,EACf,yBAAyB,EACzB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAErE,IAAI,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzC,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,kBAAkB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YACpC,MAAM,WAAW,CAAC;gBACd,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa;aAC9D,CAAC,CAAC;QACP,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACnC,MAAM,UAAU,EAAE,CAAC;QACvB,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YAC7B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACzB,CAAC;IACL,CAAC;SAAM,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,yBAAyB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACtC,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YACpC,MAAM,kBAAkB,CAAC;gBACrB,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa;gBAC3D,GAAG,EAAE,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;aACxC,CAAC,CAAC;QACP,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACnC,MAAM,iBAAiB,EAAE,CAAC;QAC9B,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAC,CAAC;YACpC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACzB,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,MAAM,OAAO,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC/C,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,OAAO,WAAW,EAAE,EAAE,CAAC,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,IAAI,OAAO,CAAC;QACZ,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACzB,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;YACpD,OAAO,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;YACxC,OAAO,CAAC,GAAG,CACP,+BAA+B,OAAO,CAAC,GAAG,wBAAwB,OAAO,CAAC,UAAU,EAAE,CACzF,CAAC;QACN,CAAC;aAAM,CAAC;YACJ,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,6BAA6B,CAAC,CAAC;YAC1E,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC/D,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC7B,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;gBAC5D,OAAO,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACJ,MAAM,EAAE,qBAAqB,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;gBAClE,OAAO,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAC;YAClD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,CAAC,IAAI,kBAAkB,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,IAAI,GAAG,GAAG,EAAE;YACd,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAC1C,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACrB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;QACP,CAAC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;AACL,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Happy2Command = {
|
|
2
|
+
kind: "all";
|
|
3
|
+
configPath?: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: "backend";
|
|
6
|
+
configPath?: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "web";
|
|
9
|
+
backendUrl: string;
|
|
10
|
+
host: string;
|
|
11
|
+
port: number;
|
|
12
|
+
trustedProxyHops: number;
|
|
13
|
+
} | {
|
|
14
|
+
kind: "help";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "invalid";
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function parseHappy2Command(arguments_: readonly string[], environment?: NodeJS.ProcessEnv): Happy2Command;
|
|
20
|
+
export declare function happy2Usage(): string;
|
|
21
|
+
//# sourceMappingURL=runnerCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runnerCommand.d.ts","sourceRoot":"","sources":["../sources/runnerCommand.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GACnB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC;IACI,IAAI,EAAE,KAAK,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;CAC5B,GACD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3C,wBAAgB,kBAAkB,CAC9B,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC7C,aAAa,CAwEf;AAED,wBAAgB,WAAW,IAAI,MAAM,CAiBpC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
export function parseHappy2Command(arguments_, environment = process.env) {
|
|
3
|
+
if (arguments_[0] === "help" || arguments_[0] === "--help" || arguments_[0] === "-h") {
|
|
4
|
+
return arguments_.length === 1
|
|
5
|
+
? { kind: "help" }
|
|
6
|
+
: { kind: "invalid", message: "Help does not accept additional arguments." };
|
|
7
|
+
}
|
|
8
|
+
const mode = arguments_[0] === "backend" || arguments_[0] === "web" ? arguments_[0] : "all";
|
|
9
|
+
const modeArguments = mode === "all" ? arguments_ : arguments_.slice(1);
|
|
10
|
+
try {
|
|
11
|
+
if (mode === "web") {
|
|
12
|
+
const { positionals, values } = parseArgs({
|
|
13
|
+
args: [...modeArguments],
|
|
14
|
+
allowPositionals: true,
|
|
15
|
+
options: {
|
|
16
|
+
"backend-url": { type: "string" },
|
|
17
|
+
help: { type: "boolean", short: "h" },
|
|
18
|
+
host: { type: "string" },
|
|
19
|
+
port: { type: "string" },
|
|
20
|
+
"trusted-proxy-hops": { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
if (values.help && positionals.length === 0)
|
|
24
|
+
return { kind: "help" };
|
|
25
|
+
if (positionals.length !== 0) {
|
|
26
|
+
return { kind: "invalid", message: "The web command has an unexpected argument." };
|
|
27
|
+
}
|
|
28
|
+
const backendUrl = values["backend-url"] ?? environment.HAPPY2_BACKEND_URL;
|
|
29
|
+
if (!backendUrl) {
|
|
30
|
+
return {
|
|
31
|
+
kind: "invalid",
|
|
32
|
+
message: "The web command requires --backend-url or HAPPY2_BACKEND_URL.",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
kind: "web",
|
|
37
|
+
backendUrl,
|
|
38
|
+
host: values.host ?? environment.HAPPY2_WEB_HOST ?? "127.0.0.1",
|
|
39
|
+
port: nonnegativeInteger(values.port ?? environment.HAPPY2_WEB_PORT ?? "3000", "web port", 65_535),
|
|
40
|
+
trustedProxyHops: nonnegativeInteger(values["trusted-proxy-hops"] ??
|
|
41
|
+
environment.HAPPY2_WEB_TRUSTED_PROXY_HOPS ??
|
|
42
|
+
"0", "trusted proxy hops"),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const { positionals, values } = parseArgs({
|
|
46
|
+
args: [...modeArguments],
|
|
47
|
+
allowPositionals: true,
|
|
48
|
+
options: {
|
|
49
|
+
config: { type: "string" },
|
|
50
|
+
help: { type: "boolean", short: "h" },
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
if (values.help && positionals.length === 0)
|
|
54
|
+
return { kind: "help" };
|
|
55
|
+
if (positionals.length !== 0) {
|
|
56
|
+
return { kind: "invalid", message: `The ${mode} command has an unexpected argument.` };
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
kind: mode,
|
|
60
|
+
configPath: values.config ?? environment.HAPPY2_CONFIG,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
kind: "invalid",
|
|
66
|
+
message: error instanceof Error ? error.message : "Invalid command arguments.",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function happy2Usage() {
|
|
71
|
+
return [
|
|
72
|
+
"Usage:",
|
|
73
|
+
" happy2 [--config /path/to/happy2.toml]",
|
|
74
|
+
" happy2 backend [--config /path/to/happy2.toml]",
|
|
75
|
+
" happy2 web --backend-url http://backend:3000 [--host 127.0.0.1] [--port 3000]",
|
|
76
|
+
" [--trusted-proxy-hops 0]",
|
|
77
|
+
" happy2 daemon start [--config /path/to/happy2.toml]",
|
|
78
|
+
" happy2 daemon stop",
|
|
79
|
+
" happy2 service start [--config /path/to/happy2.toml]",
|
|
80
|
+
" happy2 service stop",
|
|
81
|
+
"",
|
|
82
|
+
"Without a mode, Happy (2) runs the backend and bundled web app together.",
|
|
83
|
+
"The web mode serves only the bundled app and proxies /v0 to the backend URL.",
|
|
84
|
+
"Web options may also use HAPPY2_BACKEND_URL, HAPPY2_WEB_HOST, HAPPY2_WEB_PORT,",
|
|
85
|
+
"and HAPPY2_WEB_TRUSTED_PROXY_HOPS.",
|
|
86
|
+
].join("\n");
|
|
87
|
+
}
|
|
88
|
+
function nonnegativeInteger(value, label, maximum = Number.MAX_SAFE_INTEGER) {
|
|
89
|
+
if (!/^\d+$/.test(value))
|
|
90
|
+
throw new Error(`Happy (2) ${label} must be a non-negative integer.`);
|
|
91
|
+
const parsed = Number(value);
|
|
92
|
+
if (!Number.isSafeInteger(parsed) || parsed > maximum) {
|
|
93
|
+
throw new Error(`Happy (2) ${label} is outside the supported range.`);
|
|
94
|
+
}
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=runnerCommand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runnerCommand.js","sourceRoot":"","sources":["../sources/runnerCommand.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runnerCommand.test.d.ts","sourceRoot":"","sources":["../sources/runnerCommand.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { happy2Usage, parseHappy2Command } from "./runnerCommand.js";
|
|
3
|
+
describe("Happy (2) package commands", () => {
|
|
4
|
+
it("keeps the all-in-one server as the default and selects backend-only explicitly", () => {
|
|
5
|
+
expect(parseHappy2Command([], {})).toEqual({ kind: "all", configPath: undefined });
|
|
6
|
+
expect(parseHappy2Command(["--config", "happy2.toml"], {})).toEqual({
|
|
7
|
+
kind: "all",
|
|
8
|
+
configPath: "happy2.toml",
|
|
9
|
+
});
|
|
10
|
+
expect(parseHappy2Command(["backend"], { HAPPY2_CONFIG: "backend.toml" })).toEqual({
|
|
11
|
+
kind: "backend",
|
|
12
|
+
configPath: "backend.toml",
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
it("parses web-only runtime settings from flags or the environment", () => {
|
|
16
|
+
expect(parseHappy2Command([
|
|
17
|
+
"web",
|
|
18
|
+
"--backend-url",
|
|
19
|
+
"http://happy2-backend:3000",
|
|
20
|
+
"--host",
|
|
21
|
+
"0.0.0.0",
|
|
22
|
+
"--port",
|
|
23
|
+
"8080",
|
|
24
|
+
"--trusted-proxy-hops",
|
|
25
|
+
"1",
|
|
26
|
+
], {})).toEqual({
|
|
27
|
+
kind: "web",
|
|
28
|
+
backendUrl: "http://happy2-backend:3000",
|
|
29
|
+
host: "0.0.0.0",
|
|
30
|
+
port: 8080,
|
|
31
|
+
trustedProxyHops: 1,
|
|
32
|
+
});
|
|
33
|
+
expect(parseHappy2Command(["web"], {
|
|
34
|
+
HAPPY2_BACKEND_URL: "https://backend.example.com",
|
|
35
|
+
HAPPY2_WEB_HOST: "::",
|
|
36
|
+
HAPPY2_WEB_PORT: "4000",
|
|
37
|
+
HAPPY2_WEB_TRUSTED_PROXY_HOPS: "2",
|
|
38
|
+
})).toEqual({
|
|
39
|
+
kind: "web",
|
|
40
|
+
backendUrl: "https://backend.example.com",
|
|
41
|
+
host: "::",
|
|
42
|
+
port: 4000,
|
|
43
|
+
trustedProxyHops: 2,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it("rejects incomplete and invalid web commands with useful usage", () => {
|
|
47
|
+
expect(parseHappy2Command(["web"], {})).toEqual({
|
|
48
|
+
kind: "invalid",
|
|
49
|
+
message: "The web command requires --backend-url or HAPPY2_BACKEND_URL.",
|
|
50
|
+
});
|
|
51
|
+
expect(parseHappy2Command(["web", "--backend-url", "http://backend", "--port", "x"], {})).toEqual({
|
|
52
|
+
kind: "invalid",
|
|
53
|
+
message: "Happy (2) web port must be a non-negative integer.",
|
|
54
|
+
});
|
|
55
|
+
expect(happy2Usage()).toContain("happy2 backend");
|
|
56
|
+
expect(happy2Usage()).toContain("happy2 web --backend-url");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=runnerCommand.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runnerCommand.test.js","sourceRoot":"","sources":["../sources/runnerCommand.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAErE,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACtF,MAAM,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;QACnF,MAAM,CAAC,kBAAkB,CAAC,CAAC,UAAU,EAAE,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YAChE,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,aAAa;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YAC/E,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,cAAc;SAC7B,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACtE,MAAM,CACF,kBAAkB,CACd;YACI,KAAK;YACL,eAAe;YACf,4BAA4B;YAC5B,QAAQ;YACR,SAAS;YACT,QAAQ;YACR,MAAM;YACN,sBAAsB;YACtB,GAAG;SACN,EACD,EAAE,CACL,CACJ,CAAC,OAAO,CAAC;YACN,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,4BAA4B;YACxC,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,IAAI;YACV,gBAAgB,EAAE,CAAC;SACtB,CAAC,CAAC;QACH,MAAM,CACF,kBAAkB,CAAC,CAAC,KAAK,CAAC,EAAE;YACxB,kBAAkB,EAAE,6BAA6B;YACjD,eAAe,EAAE,IAAI;YACrB,eAAe,EAAE,MAAM;YACvB,6BAA6B,EAAE,GAAG;SACrC,CAAC,CACL,CAAC,OAAO,CAAC;YACN,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,6BAA6B;YACzC,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,IAAI;YACV,gBAAgB,EAAE,CAAC;SACtB,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,kBAAkB,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YAC5C,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,+DAA+D;SAC3E,CAAC,CAAC;QACH,MAAM,CACF,kBAAkB,CAAC,CAAC,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CACpF,CAAC,OAAO,CAAC;YACN,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,oDAAoD;SAChE,CAAC,CAAC;QACH,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAClD,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
+
import type { RunningHappy2 } from "./backend.js";
|
|
1
2
|
import type { ServerConfig } from "./modules/config/type.js";
|
|
2
|
-
export
|
|
3
|
-
/** Actual bound public URL. This differs from config.server.publicUrl when port 0 is used. */
|
|
4
|
-
url: string;
|
|
5
|
-
close(): Promise<void>;
|
|
6
|
-
}
|
|
3
|
+
export type StandaloneHappy2 = RunningHappy2;
|
|
7
4
|
export interface StandaloneOptions {
|
|
8
5
|
logger?: boolean;
|
|
9
6
|
webRoot?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../sources/standalone.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../sources/standalone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,6EAA6E;AAC7E,wBAAsB,qBAAqB,CACvC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,iBAAsB,GAChC,OAAO,CAAC,gBAAgB,CAAC,CA+C3B"}
|
|
@@ -1,26 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import Fastify, {} from "fastify";
|
|
4
|
-
import proxy from "@fastify/http-proxy";
|
|
5
|
-
import staticFiles from "@fastify/static";
|
|
6
|
-
import { createClient } from "@libsql/client";
|
|
7
|
-
import { TokenService } from "./modules/auth/tokens.js";
|
|
8
|
-
import { serverSchemaMigrate } from "./modules/server/serverSchemaMigrate.js";
|
|
9
|
-
import { buildServer } from "./server.js";
|
|
1
|
+
import { startBackendHappy2 } from "./backend.js";
|
|
2
|
+
import { startWebHappy2 } from "./web.js";
|
|
10
3
|
/** Starts the private API server and the public web/static reverse proxy. */
|
|
11
4
|
export async function startStandaloneHappy2(config, options = {}) {
|
|
12
|
-
const webRoot = options.webRoot ?? join(import.meta.dirname, "web");
|
|
13
|
-
await access(join(webRoot, "index.html"));
|
|
14
|
-
const client = createClient({
|
|
15
|
-
url: config.database.url,
|
|
16
|
-
authToken: config.database.authTokenEnv
|
|
17
|
-
? process.env[config.database.authTokenEnv]
|
|
18
|
-
: undefined,
|
|
19
|
-
});
|
|
20
5
|
let backend;
|
|
21
|
-
let
|
|
6
|
+
let web;
|
|
22
7
|
try {
|
|
23
|
-
await serverSchemaMigrate(client);
|
|
24
8
|
const backendConfig = {
|
|
25
9
|
...config,
|
|
26
10
|
server: {
|
|
@@ -32,63 +16,29 @@ export async function startStandaloneHappy2(config, options = {}) {
|
|
|
32
16
|
trustedProxyHops: 1,
|
|
33
17
|
},
|
|
34
18
|
};
|
|
35
|
-
backend = await
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
const backendUrl = await backend.listen({ host: "127.0.0.1", port: 0 });
|
|
41
|
-
gateway = Fastify({
|
|
19
|
+
backend = await startBackendHappy2(backendConfig, { logger: options.logger });
|
|
20
|
+
web = await startWebHappy2({
|
|
21
|
+
backendUrl: backend.url,
|
|
22
|
+
host: config.server.host,
|
|
42
23
|
logger: options.logger ?? true,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
upstream: backendUrl,
|
|
47
|
-
prefix: "/v0",
|
|
48
|
-
rewritePrefix: "/v0",
|
|
49
|
-
httpMethods: ["GET", "POST"],
|
|
50
|
-
replyOptions: {
|
|
51
|
-
rewriteRequestHeaders: (request, headers) => ({
|
|
52
|
-
...headers,
|
|
53
|
-
"x-forwarded-for": request.ip,
|
|
54
|
-
"x-forwarded-host": request.host ?? "",
|
|
55
|
-
"x-forwarded-proto": request.protocol,
|
|
56
|
-
}),
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
await gateway.register(staticFiles, {
|
|
60
|
-
root: webRoot,
|
|
61
|
-
wildcard: false,
|
|
62
|
-
index: "index.html",
|
|
63
|
-
});
|
|
64
|
-
gateway.setNotFoundHandler((request, reply) => {
|
|
65
|
-
if ((request.method === "GET" || request.method === "HEAD") &&
|
|
66
|
-
!request.url.startsWith("/v0") &&
|
|
67
|
-
request.headers.accept?.includes("text/html")) {
|
|
68
|
-
return reply.type("text/html; charset=utf-8").sendFile("index.html");
|
|
69
|
-
}
|
|
70
|
-
return reply.code(404).send({ error: "not_found" });
|
|
24
|
+
port: config.server.port,
|
|
25
|
+
trustedProxyHops: config.server.trustedProxyHops,
|
|
26
|
+
webRoot: options.webRoot,
|
|
71
27
|
});
|
|
72
|
-
const url = await gateway.listen({ host: config.server.host, port: config.server.port });
|
|
73
28
|
let closed = false;
|
|
74
29
|
const close = async () => {
|
|
75
30
|
if (closed)
|
|
76
31
|
return;
|
|
77
32
|
closed = true;
|
|
78
33
|
try {
|
|
79
|
-
await
|
|
34
|
+
await web?.close();
|
|
80
35
|
}
|
|
81
36
|
finally {
|
|
82
|
-
|
|
83
|
-
await backend?.close();
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
client.close();
|
|
87
|
-
}
|
|
37
|
+
await backend?.close();
|
|
88
38
|
}
|
|
89
39
|
};
|
|
90
40
|
return {
|
|
91
|
-
url,
|
|
41
|
+
url: web.url,
|
|
92
42
|
close,
|
|
93
43
|
async [Symbol.asyncDispose]() {
|
|
94
44
|
await close();
|
|
@@ -96,9 +46,8 @@ export async function startStandaloneHappy2(config, options = {}) {
|
|
|
96
46
|
};
|
|
97
47
|
}
|
|
98
48
|
catch (error) {
|
|
99
|
-
await
|
|
49
|
+
await web?.close().catch(() => undefined);
|
|
100
50
|
await backend?.close().catch(() => undefined);
|
|
101
|
-
client.close();
|
|
102
51
|
throw error;
|
|
103
52
|
}
|
|
104
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../sources/standalone.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../sources/standalone.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAS1C,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACvC,MAAoB,EACpB,OAAO,GAAsB,EAAE;IAE/B,IAAI,OAAkC,CAAC;IACvC,IAAI,GAA8B,CAAC;IACnC,IAAI,CAAC;QACD,MAAM,aAAa,GAAiB;YAChC,GAAG,MAAM;YACT,MAAM,EAAE;gBACJ,GAAG,MAAM,CAAC,MAAM;gBAChB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,CAAC;gBACP,qEAAqE;gBACrE,yEAAyE;gBACzE,gBAAgB,EAAE,CAAC;aACtB;SACJ,CAAC;QACF,OAAO,GAAG,MAAM,kBAAkB,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9E,GAAG,GAAG,MAAM,cAAc,CAAC;YACvB,UAAU,EAAE,OAAO,CAAC,GAAG;YACvB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI;YACxB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;YAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI;YACxB,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,gBAAgB;YAChD,OAAO,EAAE,OAAO,CAAC,OAAO;SAC3B,CAAC,CAAC;QAEH,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE;YACrB,IAAI,MAAM;gBAAE,OAAO;YACnB,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,CAAC;gBACD,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC;YACvB,CAAC;oBAAS,CAAC;gBACP,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC;YAC3B,CAAC;QACL,CAAC,CAAC;QACF,OAAO;YACH,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,KAAK;YACL,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;gBACvB,MAAM,KAAK,EAAE,CAAC;YAClB,CAAC;SACJ,CAAC;IACN,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC"}
|