rollbridge 0.1.2 → 0.1.4
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/LICENSE +21 -0
- package/README.md +172 -5
- package/TODO.md +16 -13
- package/docs/cli.md +151 -0
- package/docs/config.md +128 -0
- package/docs/deploy-recipes.md +102 -0
- package/docs/troubleshooting.md +102 -0
- package/package.json +20 -1
- package/src/cli.js +141 -2
- package/src/config.js +73 -6
- package/src/daemon.js +61 -6
- package/src/doctor.js +114 -0
- package/src/health.js +4 -0
- package/src/managed-process.js +9 -2
- package/src/release-group.js +33 -4
- package/test/config-validation.test.js +105 -0
- package/test/doctor.test.js +228 -0
- package/test/fixtures/crasher.js +2 -0
- package/test/health.test.js +63 -0
- package/test/logs.test.js +99 -0
- package/test/managed-process.test.js +60 -0
- package/test/package-metadata.test.js +29 -0
- package/test/release-retention.test.js +107 -0
- package/test/rollbridge.test.js +56 -5
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict"
|
|
4
|
+
import fs from "node:fs/promises"
|
|
5
|
+
import os from "node:os"
|
|
6
|
+
import path from "node:path"
|
|
7
|
+
import test from "node:test"
|
|
8
|
+
import {fileURLToPath} from "node:url"
|
|
9
|
+
import RollbridgeDaemon from "../src/daemon.js"
|
|
10
|
+
import {normalizeConfig} from "../src/config.js"
|
|
11
|
+
import {formatLogSources, runCli} from "../src/cli.js"
|
|
12
|
+
|
|
13
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
14
|
+
const dummyAppPath = path.join(currentDir, "fixtures", "dummy-app.js")
|
|
15
|
+
|
|
16
|
+
test("formatLogSources renders a section per process with timestamped lines", () => {
|
|
17
|
+
const output = formatLogSources([
|
|
18
|
+
{id: "web", logs: [{at: "2026-05-22T00:00:00.000Z", line: "listening", stream: "stdout"}], source: "release v1 (active)"},
|
|
19
|
+
{id: "beacon", logs: [], source: "service"}
|
|
20
|
+
], undefined)
|
|
21
|
+
|
|
22
|
+
assert.match(output, /== web \[release v1 \(active\)\] ==/)
|
|
23
|
+
assert.match(output, /2026-05-22T00:00:00\.000Z \[stdout\] listening/)
|
|
24
|
+
assert.match(output, /== beacon \[service\] ==/)
|
|
25
|
+
assert.match(output, /\(no recent output\)/)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("formatLogSources filters to a single process id", () => {
|
|
29
|
+
const sources = [
|
|
30
|
+
{id: "web", logs: [], source: "release v1 (active)"},
|
|
31
|
+
{id: "beacon", logs: [], source: "service"}
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const output = formatLogSources(sources, "web")
|
|
35
|
+
|
|
36
|
+
assert.match(output, /== web /)
|
|
37
|
+
assert.doesNotMatch(output, /beacon/)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("formatLogSources reports when there are no processes or no match", () => {
|
|
41
|
+
assert.equal(formatLogSources([], undefined), "No managed processes.")
|
|
42
|
+
assert.equal(
|
|
43
|
+
formatLogSources([{id: "web", logs: [], source: "release v1 (active)"}], "missing"),
|
|
44
|
+
'No process found with id "missing".'
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test("logs CLI prints captured output per managed process", async () => {
|
|
49
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-logs-"))
|
|
50
|
+
const socketPath = path.join(root, "rollbridge.sock")
|
|
51
|
+
const rawConfig = {
|
|
52
|
+
application: "rollbridge-logs-test",
|
|
53
|
+
control: {path: socketPath},
|
|
54
|
+
processes: [
|
|
55
|
+
{
|
|
56
|
+
command: `${JSON.stringify(process.execPath)} ${JSON.stringify(dummyAppPath)}`,
|
|
57
|
+
health: {intervalMs: 50, path: "/ping", timeoutMs: 3000},
|
|
58
|
+
id: "web",
|
|
59
|
+
policy: "proxied",
|
|
60
|
+
port: {from: 0, to: 0}
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
proxy: {host: "127.0.0.1", port: 0}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// CommonJS config file so the CLI can load it from a temp dir on any Node version.
|
|
67
|
+
await fs.writeFile(path.join(root, "rollbridge.js"), `module.exports = ${JSON.stringify(rawConfig)}\n`)
|
|
68
|
+
|
|
69
|
+
const daemon = new RollbridgeDaemon({config: normalizeConfig(rawConfig), logger: () => {}})
|
|
70
|
+
|
|
71
|
+
await daemon.start()
|
|
72
|
+
|
|
73
|
+
const originalLog = console.log
|
|
74
|
+
/** @type {string[]} */
|
|
75
|
+
const lines = []
|
|
76
|
+
|
|
77
|
+
console.log = (/** @type {string[]} */ ...args) => { lines.push(args.map((arg) => String(arg)).join(" ")) }
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await daemon.deploy({releaseId: "v1", releasePath: root, revision: "v1"})
|
|
81
|
+
await runCli(["node", "rollbridge", "logs", "-c", path.join(root, "rollbridge.js")])
|
|
82
|
+
|
|
83
|
+
assert.match(lines.join("\n"), /== web \[release v1 \(active\)\] ==/)
|
|
84
|
+
|
|
85
|
+
lines.length = 0
|
|
86
|
+
await runCli(["node", "rollbridge", "logs", "--json", "-c", path.join(root, "rollbridge.js")])
|
|
87
|
+
|
|
88
|
+
const parsed = JSON.parse(lines.join("\n"))
|
|
89
|
+
const web = parsed.find((/** @type {{id: string, logs: import("../src/managed-process.js").ManagedProcessLog[], source: string}} */ entry) => entry.id === "web")
|
|
90
|
+
|
|
91
|
+
assert.ok(web, "expected a web entry in the JSON output")
|
|
92
|
+
assert.match(web.source, /release v1 \(active\)/)
|
|
93
|
+
assert.ok(Array.isArray(web.logs))
|
|
94
|
+
} finally {
|
|
95
|
+
console.log = originalLog
|
|
96
|
+
await daemon.shutdown()
|
|
97
|
+
await fs.rm(root, {force: true, recursive: true})
|
|
98
|
+
}
|
|
99
|
+
})
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict"
|
|
4
|
+
import path from "node:path"
|
|
4
5
|
import test from "node:test"
|
|
6
|
+
import {fileURLToPath} from "node:url"
|
|
5
7
|
import ManagedProcess from "../src/managed-process.js"
|
|
6
8
|
|
|
9
|
+
const crasherPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures", "crasher.js")
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
12
|
* Builds a managed process that is never spawned, for exercising output retention directly.
|
|
9
13
|
* @param {number} outputLines - Recent output lines to retain and report.
|
|
@@ -23,6 +27,21 @@ function buildProcess(outputLines) {
|
|
|
23
27
|
})
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @param {() => boolean} callback - Probe.
|
|
32
|
+
* @returns {Promise<void>} Resolves once the probe returns true.
|
|
33
|
+
*/
|
|
34
|
+
async function waitFor(callback) {
|
|
35
|
+
const deadline = Date.now() + 3000
|
|
36
|
+
|
|
37
|
+
while (Date.now() < deadline) {
|
|
38
|
+
if (callback()) return
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new Error("Timed out waiting for condition")
|
|
43
|
+
}
|
|
44
|
+
|
|
26
45
|
test("retains and reports only the configured number of recent output lines", () => {
|
|
27
46
|
const managed = buildProcess(3)
|
|
28
47
|
|
|
@@ -44,3 +63,44 @@ test("keeps every output line when fewer than the retention limit are produced",
|
|
|
44
63
|
|
|
45
64
|
assert.deepEqual(logs.map((entry) => entry.line), ["one", "two"])
|
|
46
65
|
})
|
|
66
|
+
|
|
67
|
+
test("reports zeroed restart and uptime fields before the process starts", () => {
|
|
68
|
+
const status = buildProcess(50).status()
|
|
69
|
+
|
|
70
|
+
assert.equal(status.restarts, 0)
|
|
71
|
+
assert.equal(status.startedAt, undefined)
|
|
72
|
+
assert.equal(status.uptimeMs, undefined)
|
|
73
|
+
assert.equal(status.state, "stopped")
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("counts automatic restarts and reports startedAt and uptime while running", async () => {
|
|
77
|
+
const managed = new ManagedProcess({
|
|
78
|
+
command: `${JSON.stringify(process.execPath)} ${JSON.stringify(crasherPath)}`,
|
|
79
|
+
cwd: undefined,
|
|
80
|
+
env: {},
|
|
81
|
+
id: "crasher",
|
|
82
|
+
logger: () => {},
|
|
83
|
+
outputLines: 50,
|
|
84
|
+
restartDelayMs: 20,
|
|
85
|
+
shouldRestart: () => true,
|
|
86
|
+
stopTimeoutMs: 500
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await managed.start()
|
|
91
|
+
|
|
92
|
+
const initial = managed.status()
|
|
93
|
+
|
|
94
|
+
assert.equal(initial.restarts, 0)
|
|
95
|
+
assert.equal(initial.state, "running")
|
|
96
|
+
assert.equal(typeof initial.startedAt, "string")
|
|
97
|
+
assert.ok(typeof initial.uptimeMs === "number" && initial.uptimeMs >= 0)
|
|
98
|
+
|
|
99
|
+
// The fixture exits non-zero ~40ms after each start, so it keeps auto-restarting.
|
|
100
|
+
await waitFor(() => managed.status().restarts >= 2)
|
|
101
|
+
|
|
102
|
+
assert.ok(managed.status().restarts >= 2)
|
|
103
|
+
} finally {
|
|
104
|
+
await managed.stop()
|
|
105
|
+
}
|
|
106
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict"
|
|
4
|
+
import fs from "node:fs/promises"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
import test from "node:test"
|
|
7
|
+
import {fileURLToPath} from "node:url"
|
|
8
|
+
|
|
9
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
10
|
+
|
|
11
|
+
test("package.json declares publish metadata", async () => {
|
|
12
|
+
const pkg = JSON.parse(await fs.readFile(path.join(repoRoot, "package.json"), "utf8"))
|
|
13
|
+
|
|
14
|
+
assert.equal(pkg.name, "rollbridge")
|
|
15
|
+
assert.equal(pkg.license, "MIT")
|
|
16
|
+
assert.equal(pkg.homepage, "https://github.com/kaspernj/rollbridge#readme")
|
|
17
|
+
assert.equal(pkg.bugs.url, "https://github.com/kaspernj/rollbridge/issues")
|
|
18
|
+
assert.equal(pkg.repository.type, "git")
|
|
19
|
+
assert.match(pkg.repository.url, /github\.com\/kaspernj\/rollbridge/)
|
|
20
|
+
assert.ok(typeof pkg.author === "string" && pkg.author.length > 0)
|
|
21
|
+
assert.ok(Array.isArray(pkg.keywords) && pkg.keywords.length > 0)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("a LICENSE file matching the declared license exists", async () => {
|
|
25
|
+
const license = await fs.readFile(path.join(repoRoot, "LICENSE"), "utf8")
|
|
26
|
+
|
|
27
|
+
assert.match(license, /MIT License/)
|
|
28
|
+
assert.match(license, /Copyright \(c\) \d{4} kaspernj/)
|
|
29
|
+
})
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict"
|
|
4
|
+
import fs from "node:fs/promises"
|
|
5
|
+
import os from "node:os"
|
|
6
|
+
import path from "node:path"
|
|
7
|
+
import test from "node:test"
|
|
8
|
+
import {fileURLToPath} from "node:url"
|
|
9
|
+
import RollbridgeDaemon, {releasesToPrune} from "../src/daemon.js"
|
|
10
|
+
import {normalizeConfig} from "../src/config.js"
|
|
11
|
+
|
|
12
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
13
|
+
const dummyAppPath = path.join(currentDir, "fixtures", "dummy-app.js")
|
|
14
|
+
|
|
15
|
+
test("releasesToPrune keeps the most recent stopped releases and never active or draining ones", () => {
|
|
16
|
+
const releases = [
|
|
17
|
+
{releaseId: "active", state: "active", stoppedAt: undefined},
|
|
18
|
+
{releaseId: "draining", state: "draining", stoppedAt: undefined},
|
|
19
|
+
{releaseId: "v3", state: "stopped", stoppedAt: "2026-05-22T00:00:03.000Z"},
|
|
20
|
+
{releaseId: "v2", state: "stopped", stoppedAt: "2026-05-22T00:00:02.000Z"},
|
|
21
|
+
{releaseId: "v1", state: "stopped", stoppedAt: "2026-05-22T00:00:01.000Z"}
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const remove = releasesToPrune(releases, {keep: 1, maxAgeMs: 0}, Date.parse("2026-05-22T00:00:10.000Z"))
|
|
25
|
+
|
|
26
|
+
assert.deepEqual([...remove].sort(), ["v1", "v2"])
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("releasesToPrune keeps the later-deployed release when stoppedAt ties", () => {
|
|
30
|
+
const sameTime = "2026-05-22T00:00:05.000Z"
|
|
31
|
+
// Deploy order (oldest first): v1 then v2, both stopped in the same millisecond.
|
|
32
|
+
const releases = [
|
|
33
|
+
{releaseId: "v1", state: "stopped", stoppedAt: sameTime},
|
|
34
|
+
{releaseId: "v2", state: "stopped", stoppedAt: sameTime}
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const remove = releasesToPrune(releases, {keep: 1, maxAgeMs: 0}, Date.parse("2026-05-22T00:00:10.000Z"))
|
|
38
|
+
|
|
39
|
+
assert.deepEqual(remove, ["v1"])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("releasesToPrune prunes stopped releases older than maxAgeMs", () => {
|
|
43
|
+
const now = Date.parse("2026-05-22T00:01:00.000Z")
|
|
44
|
+
const releases = [
|
|
45
|
+
{releaseId: "fresh", state: "stopped", stoppedAt: new Date(now - 1000).toISOString()},
|
|
46
|
+
{releaseId: "old", state: "stopped", stoppedAt: new Date(now - 60000).toISOString()}
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
const remove = releasesToPrune(releases, {keep: 100, maxAgeMs: 30000}, now)
|
|
50
|
+
|
|
51
|
+
assert.deepEqual(remove, ["old"])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("the daemon prunes stopped releases beyond the retention count across deploys", async () => {
|
|
55
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-retention-"))
|
|
56
|
+
const config = normalizeConfig({
|
|
57
|
+
application: "rollbridge-retention-test",
|
|
58
|
+
control: {path: path.join(root, "rollbridge.sock")},
|
|
59
|
+
processes: [
|
|
60
|
+
{
|
|
61
|
+
command: `${JSON.stringify(process.execPath)} ${JSON.stringify(dummyAppPath)}`,
|
|
62
|
+
health: {intervalMs: 50, path: "/ping", timeoutMs: 3000},
|
|
63
|
+
id: "web",
|
|
64
|
+
policy: "proxied",
|
|
65
|
+
port: {from: 0, to: 0}
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
proxy: {drainTimeoutMs: 200, forceStopTimeoutMs: 200, host: "127.0.0.1", port: 0},
|
|
69
|
+
releaseRetention: {keep: 1}
|
|
70
|
+
})
|
|
71
|
+
const daemon = new RollbridgeDaemon({config, logger: () => {}})
|
|
72
|
+
|
|
73
|
+
await daemon.start()
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await daemon.deploy({releaseId: "v1", releasePath: root, revision: "v1"})
|
|
77
|
+
await daemon.deploy({releaseId: "v2", releasePath: root, revision: "v2"})
|
|
78
|
+
await daemon.deploy({releaseId: "v3", releasePath: root, revision: "v3"})
|
|
79
|
+
|
|
80
|
+
// Older stopped releases are pruned once they drain; keep:1 retains only the most recent stopped one.
|
|
81
|
+
await waitFor(() => !daemon.status().releases.some((release) => release.releaseId === "v1"))
|
|
82
|
+
|
|
83
|
+
const ids = daemon.status().releases.map((release) => release.releaseId)
|
|
84
|
+
|
|
85
|
+
assert.ok(ids.includes("v3"), `active release should be retained, got ${JSON.stringify(ids)}`)
|
|
86
|
+
assert.ok(!ids.includes("v1"), `oldest stopped release should be pruned, got ${JSON.stringify(ids)}`)
|
|
87
|
+
assert.ok(ids.length <= 2, `expected at most the active release plus one stopped, got ${JSON.stringify(ids)}`)
|
|
88
|
+
} finally {
|
|
89
|
+
await daemon.shutdown()
|
|
90
|
+
await fs.rm(root, {force: true, recursive: true})
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {() => boolean} callback - Probe.
|
|
96
|
+
* @returns {Promise<void>} Resolves once the probe returns true.
|
|
97
|
+
*/
|
|
98
|
+
async function waitFor(callback) {
|
|
99
|
+
const deadline = Date.now() + 3000
|
|
100
|
+
|
|
101
|
+
while (Date.now() < deadline) {
|
|
102
|
+
if (callback()) return
|
|
103
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error("Timed out waiting for condition")
|
|
107
|
+
}
|
package/test/rollbridge.test.js
CHANGED
|
@@ -64,6 +64,57 @@ test("failed health check leaves the previous release active", async () => {
|
|
|
64
64
|
}
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
+
test("wildcard proxy bind host targets release processes through loopback", async () => {
|
|
68
|
+
const fixture = await createFixture({proxyHost: "0.0.0.0"})
|
|
69
|
+
const daemon = await startDaemon(fixture.config)
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await daemon.deploy({releaseId: "v1", releasePath: fixture.root, revision: "v1"})
|
|
73
|
+
|
|
74
|
+
const status = daemon.status()
|
|
75
|
+
const release = statusRelease(daemon, "v1")
|
|
76
|
+
|
|
77
|
+
assert.ok(daemon.activeRelease, "expected active release")
|
|
78
|
+
assert.equal(status.proxy.host, "0.0.0.0")
|
|
79
|
+
assert.equal(status.proxy.upstreamHost, "127.0.0.1")
|
|
80
|
+
assert.equal(daemon.activeRelease.proxyTarget().target, `http://127.0.0.1:${release.ports.web}`)
|
|
81
|
+
assert.equal(await fetchText(daemon, "/release"), "v1")
|
|
82
|
+
} finally {
|
|
83
|
+
await daemon.shutdown()
|
|
84
|
+
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test("failed release startup logs process output before cleanup", async () => {
|
|
89
|
+
const fixture = await createFixture({webCommand: `${JSON.stringify(process.execPath)} -e "console.log('startup stdout'); console.error('startup stderr'); const http = require('node:http'); http.createServer((_request, response) => { response.writeHead(500); response.end('bad') }).listen(Number(process.env.ROLLBRIDGE_PORT), '127.0.0.1')"`, webHealthTimeoutMs: 500})
|
|
90
|
+
/** @type {Array<{data?: Record<string, import("../src/json.js").JsonValue>, message: string}>} */
|
|
91
|
+
const logs = []
|
|
92
|
+
const daemon = new RollbridgeDaemon({
|
|
93
|
+
config: fixture.config,
|
|
94
|
+
logger: (message, data = {}) => logs.push({data, message})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
await daemon.start()
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await assert.rejects(
|
|
101
|
+
() => daemon.deploy({releaseId: "bad", releasePath: fixture.root, revision: "bad"}),
|
|
102
|
+
/Health check failed/
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const processStatusLog = logs.find((entry) => entry.message === "release startup process status" && entry.data?.processId === "web")
|
|
106
|
+
|
|
107
|
+
assert.ok(processStatusLog, "expected failed web process diagnostics to be logged")
|
|
108
|
+
assert.ok(processStatusLog.data, "expected diagnostic data")
|
|
109
|
+
assert.ok(Array.isArray(processStatusLog.data.logs), "expected retained process output in diagnostics")
|
|
110
|
+
assert.ok(processStatusLog.data.logs.some((entry) => typeof entry === "object" && entry && "line" in entry && entry.line === "startup stdout"))
|
|
111
|
+
assert.ok(processStatusLog.data.logs.some((entry) => typeof entry === "object" && entry && "line" in entry && entry.line === "startup stderr"))
|
|
112
|
+
} finally {
|
|
113
|
+
await daemon.shutdown()
|
|
114
|
+
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
67
118
|
test("singleton processes restart without overlap during deploy", async () => {
|
|
68
119
|
const fixture = await createFixture({includeSingleton: true})
|
|
69
120
|
const daemon = await startDaemon(fixture.config)
|
|
@@ -285,7 +336,7 @@ test("deploy can ensure the daemon before sending the release command", async ()
|
|
|
285
336
|
})
|
|
286
337
|
|
|
287
338
|
/**
|
|
288
|
-
* @param {{includeService?: boolean, includeSingleton?: boolean, webDependsOnService?: boolean}} [options] - Fixture options.
|
|
339
|
+
* @param {{includeService?: boolean, includeSingleton?: boolean, proxyHost?: string, webCommand?: string, webDependsOnService?: boolean, webHealthTimeoutMs?: number}} [options] - Fixture options.
|
|
289
340
|
* @returns {Promise<{config: import("../src/config.js").RollbridgeConfig, root: string, serviceLogPath: string, singletonLogPath: string}>} Fixture data.
|
|
290
341
|
*/
|
|
291
342
|
async function createFixture(options = {}) {
|
|
@@ -309,13 +360,13 @@ async function createFixture(options = {}) {
|
|
|
309
360
|
}
|
|
310
361
|
|
|
311
362
|
processes.push({
|
|
312
|
-
command: options.webDependsOnService
|
|
363
|
+
command: options.webCommand || (options.webDependsOnService
|
|
313
364
|
? `${JSON.stringify(process.execPath)} ${JSON.stringify(dependentAppPath)}`
|
|
314
|
-
: `${JSON.stringify(process.execPath)} ${JSON.stringify(dummyAppPath)}
|
|
365
|
+
: `${JSON.stringify(process.execPath)} ${JSON.stringify(dummyAppPath)}`),
|
|
315
366
|
health: {
|
|
316
367
|
intervalMs: 50,
|
|
317
368
|
path: "/ping",
|
|
318
|
-
timeoutMs: 3000
|
|
369
|
+
timeoutMs: options.webHealthTimeoutMs || 3000
|
|
319
370
|
},
|
|
320
371
|
id: "web",
|
|
321
372
|
policy: "proxied",
|
|
@@ -344,7 +395,7 @@ async function createFixture(options = {}) {
|
|
|
344
395
|
forceStopTimeoutMs: 500,
|
|
345
396
|
healthPath: "/ping",
|
|
346
397
|
healthTimeoutMs: 3000,
|
|
347
|
-
host: "127.0.0.1",
|
|
398
|
+
host: options.proxyHost || "127.0.0.1",
|
|
348
399
|
port: 0
|
|
349
400
|
}
|
|
350
401
|
})
|