rollbridge 0.1.1 → 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 +227 -5
- package/TODO.md +23 -19
- 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/examples/rollbridge.service +48 -0
- package/package.json +20 -1
- package/src/cli.js +141 -2
- package/src/config.js +119 -9
- package/src/daemon.js +66 -6
- package/src/doctor.js +114 -0
- package/src/health.js +4 -0
- package/src/managed-process.js +17 -7
- package/src/release-group.js +35 -4
- package/test/config-validation.test.js +167 -0
- package/test/control-protocol.test.js +94 -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 +106 -0
- package/test/package-metadata.test.js +29 -0
- package/test/proxy.test.js +128 -0
- package/test/release-group.test.js +58 -0
- package/test/release-retention.test.js +107 -0
- package/test/rollbridge.test.js +79 -5
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
|
|
12
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
13
|
+
const dummyAppPath = path.join(currentDir, "fixtures", "dummy-app.js")
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} root - Fixture root used for the control socket.
|
|
17
|
+
* @param {number} restartDelayMs - Delay before a crashed process is restarted.
|
|
18
|
+
* @returns {import("../src/config.js").RollbridgeConfig} Normalized config with one proxied web process.
|
|
19
|
+
*/
|
|
20
|
+
function buildConfig(root, restartDelayMs) {
|
|
21
|
+
return normalizeConfig({
|
|
22
|
+
application: "rollbridge-proxy-test",
|
|
23
|
+
control: {path: path.join(root, "rollbridge.sock")},
|
|
24
|
+
processes: [
|
|
25
|
+
{
|
|
26
|
+
command: `${JSON.stringify(process.execPath)} ${JSON.stringify(dummyAppPath)}`,
|
|
27
|
+
health: {intervalMs: 50, path: "/ping", timeoutMs: 3000},
|
|
28
|
+
id: "web",
|
|
29
|
+
policy: "proxied",
|
|
30
|
+
port: {from: 0, to: 0},
|
|
31
|
+
restartDelayMs
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
proxy: {drainTimeoutMs: 1000, forceStopTimeoutMs: 500, healthPath: "/ping", healthTimeoutMs: 3000, host: "127.0.0.1", port: 0}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {RollbridgeDaemon} daemon - Daemon.
|
|
40
|
+
* @param {string} pathName - Request path.
|
|
41
|
+
* @returns {Promise<Response>} Proxy response.
|
|
42
|
+
*/
|
|
43
|
+
async function proxyFetch(daemon, pathName) {
|
|
44
|
+
return await fetch(`http://127.0.0.1:${daemon.getProxyPort()}${pathName}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {RollbridgeDaemon} daemon - Daemon.
|
|
49
|
+
* @param {string} releaseId - Active release id.
|
|
50
|
+
* @returns {number} The web process pid reported by status.
|
|
51
|
+
*/
|
|
52
|
+
function webPid(daemon, releaseId) {
|
|
53
|
+
const release = daemon.status().releases.find((candidate) => candidate.releaseId === releaseId)
|
|
54
|
+
|
|
55
|
+
assert.ok(release, `Release ${releaseId} should be present`)
|
|
56
|
+
|
|
57
|
+
const web = release.processes.find((candidate) => candidate.id === "web")
|
|
58
|
+
|
|
59
|
+
assert.ok(web && typeof web.pid === "number", "web process should report a pid")
|
|
60
|
+
|
|
61
|
+
return web.pid
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {() => Promise<boolean> | boolean} callback - Probe.
|
|
66
|
+
* @returns {Promise<void>} Resolves once the probe returns true.
|
|
67
|
+
*/
|
|
68
|
+
async function waitFor(callback) {
|
|
69
|
+
const deadline = Date.now() + 3000
|
|
70
|
+
|
|
71
|
+
while (Date.now() < deadline) {
|
|
72
|
+
if (await callback()) return
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error("Timed out waiting for condition")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
test("proxy returns 502 while the active release web process is down", async () => {
|
|
80
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-proxy-"))
|
|
81
|
+
const daemon = new RollbridgeDaemon({config: buildConfig(root, 60000), logger: () => {}})
|
|
82
|
+
|
|
83
|
+
await daemon.start()
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await daemon.deploy({releaseId: "v1", releasePath: root, revision: "v1"})
|
|
87
|
+
assert.equal((await proxyFetch(daemon, "/release")).status, 200)
|
|
88
|
+
|
|
89
|
+
// Kill the web process group so the active release exits unexpectedly; restart is held off for 60s.
|
|
90
|
+
process.kill(-webPid(daemon, "v1"), "SIGKILL")
|
|
91
|
+
|
|
92
|
+
let lastStatus = 0
|
|
93
|
+
|
|
94
|
+
await waitFor(async () => {
|
|
95
|
+
lastStatus = (await proxyFetch(daemon, "/release")).status
|
|
96
|
+
|
|
97
|
+
return lastStatus === 502
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
assert.equal(lastStatus, 502)
|
|
101
|
+
} finally {
|
|
102
|
+
await daemon.shutdown()
|
|
103
|
+
await fs.rm(root, {force: true, recursive: true})
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test("proxy recovers once the crashed web process restarts", async () => {
|
|
108
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-proxy-"))
|
|
109
|
+
const daemon = new RollbridgeDaemon({config: buildConfig(root, 300), logger: () => {}})
|
|
110
|
+
|
|
111
|
+
await daemon.start()
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
await daemon.deploy({releaseId: "v1", releasePath: root, revision: "v1"})
|
|
115
|
+
assert.equal((await proxyFetch(daemon, "/release")).status, 200)
|
|
116
|
+
|
|
117
|
+
process.kill(-webPid(daemon, "v1"), "SIGKILL")
|
|
118
|
+
|
|
119
|
+
// First confirm the crash actually takes the proxy down (502), so passing requires
|
|
120
|
+
// observing the outage, then confirm the restart brings the proxy back to 200.
|
|
121
|
+
await waitFor(async () => (await proxyFetch(daemon, "/release")).status === 502)
|
|
122
|
+
await waitFor(async () => (await proxyFetch(daemon, "/release")).status === 200)
|
|
123
|
+
assert.equal((await proxyFetch(daemon, "/release")).status, 200)
|
|
124
|
+
} finally {
|
|
125
|
+
await daemon.shutdown()
|
|
126
|
+
await fs.rm(root, {force: true, recursive: true})
|
|
127
|
+
}
|
|
128
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict"
|
|
4
|
+
import test from "node:test"
|
|
5
|
+
import ReleaseGroup from "../src/release-group.js"
|
|
6
|
+
import {normalizeConfig} from "../src/config.js"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import("../src/json.js").JsonValue} webProcess - The single proxied process definition.
|
|
10
|
+
* @returns {ReleaseGroup} A release group ready for buildProcess.
|
|
11
|
+
*/
|
|
12
|
+
function buildRelease(webProcess) {
|
|
13
|
+
const config = normalizeConfig({
|
|
14
|
+
application: "demo",
|
|
15
|
+
control: {path: "/tmp/rollbridge-release-group.sock"},
|
|
16
|
+
processes: [webProcess],
|
|
17
|
+
proxy: {host: "127.0.0.1", port: 0}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return new ReleaseGroup({config, logger: () => {}, releaseId: "v1", releasePath: "/tmp/rel", revision: "v1"})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test("templates interpolate values from the daemon environment", () => {
|
|
24
|
+
const release = buildRelease({
|
|
25
|
+
command: "run --token {{env.ROLLBRIDGE_ENV_TEST}}",
|
|
26
|
+
env: {DOWNSTREAM_TOKEN: "{{env.ROLLBRIDGE_ENV_TEST}}"},
|
|
27
|
+
id: "web",
|
|
28
|
+
policy: "proxied",
|
|
29
|
+
port: {from: 0, to: 0}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
process.env.ROLLBRIDGE_ENV_TEST = "from-daemon"
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const managed = release.buildProcess(release.config.processes[0])
|
|
36
|
+
|
|
37
|
+
assert.equal(managed.command, "run --token from-daemon")
|
|
38
|
+
assert.equal(managed.env.DOWNSTREAM_TOKEN, "from-daemon")
|
|
39
|
+
} finally {
|
|
40
|
+
delete process.env.ROLLBRIDGE_ENV_TEST
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("a referenced daemon environment variable that is unset fails fast", () => {
|
|
45
|
+
const release = buildRelease({
|
|
46
|
+
command: "run {{env.ROLLBRIDGE_ENV_MISSING}}",
|
|
47
|
+
id: "web",
|
|
48
|
+
policy: "proxied",
|
|
49
|
+
port: {from: 0, to: 0}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
delete process.env.ROLLBRIDGE_ENV_MISSING
|
|
53
|
+
|
|
54
|
+
assert.throws(
|
|
55
|
+
() => release.buildProcess(release.config.processes[0]),
|
|
56
|
+
/Missing template value for \{\{env.ROLLBRIDGE_ENV_MISSING\}\}/
|
|
57
|
+
)
|
|
58
|
+
})
|
|
@@ -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)
|
|
@@ -213,6 +264,29 @@ test("a control socket held by a non-Rollbridge process reports a generic confli
|
|
|
213
264
|
}
|
|
214
265
|
})
|
|
215
266
|
|
|
267
|
+
test("applies the configured control socket permission mode", async () => {
|
|
268
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-test-"))
|
|
269
|
+
const socketPath = path.join(root, "rollbridge.sock")
|
|
270
|
+
const config = normalizeConfig({
|
|
271
|
+
application: "rollbridge-test",
|
|
272
|
+
control: {mode: "660", path: socketPath},
|
|
273
|
+
processes: [{command: "true", id: "web", policy: "proxied", port: {from: 0, to: 0}}],
|
|
274
|
+
proxy: {host: "127.0.0.1", port: 0}
|
|
275
|
+
})
|
|
276
|
+
const daemon = new RollbridgeDaemon({config, logger: () => {}})
|
|
277
|
+
|
|
278
|
+
await daemon.start()
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const stats = await fs.stat(socketPath)
|
|
282
|
+
|
|
283
|
+
assert.equal(stats.mode & 0o777, 0o660)
|
|
284
|
+
} finally {
|
|
285
|
+
await daemon.shutdown()
|
|
286
|
+
await fs.rm(root, {force: true, recursive: true})
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
216
290
|
test("deploy can ensure the daemon before sending the release command", async () => {
|
|
217
291
|
const fixture = await createFixture()
|
|
218
292
|
const configPath = await writeConfigFile(fixture.config, fixture.root)
|
|
@@ -262,7 +336,7 @@ test("deploy can ensure the daemon before sending the release command", async ()
|
|
|
262
336
|
})
|
|
263
337
|
|
|
264
338
|
/**
|
|
265
|
-
* @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.
|
|
266
340
|
* @returns {Promise<{config: import("../src/config.js").RollbridgeConfig, root: string, serviceLogPath: string, singletonLogPath: string}>} Fixture data.
|
|
267
341
|
*/
|
|
268
342
|
async function createFixture(options = {}) {
|
|
@@ -286,13 +360,13 @@ async function createFixture(options = {}) {
|
|
|
286
360
|
}
|
|
287
361
|
|
|
288
362
|
processes.push({
|
|
289
|
-
command: options.webDependsOnService
|
|
363
|
+
command: options.webCommand || (options.webDependsOnService
|
|
290
364
|
? `${JSON.stringify(process.execPath)} ${JSON.stringify(dependentAppPath)}`
|
|
291
|
-
: `${JSON.stringify(process.execPath)} ${JSON.stringify(dummyAppPath)}
|
|
365
|
+
: `${JSON.stringify(process.execPath)} ${JSON.stringify(dummyAppPath)}`),
|
|
292
366
|
health: {
|
|
293
367
|
intervalMs: 50,
|
|
294
368
|
path: "/ping",
|
|
295
|
-
timeoutMs: 3000
|
|
369
|
+
timeoutMs: options.webHealthTimeoutMs || 3000
|
|
296
370
|
},
|
|
297
371
|
id: "web",
|
|
298
372
|
policy: "proxied",
|
|
@@ -321,7 +395,7 @@ async function createFixture(options = {}) {
|
|
|
321
395
|
forceStopTimeoutMs: 500,
|
|
322
396
|
healthPath: "/ping",
|
|
323
397
|
healthTimeoutMs: 3000,
|
|
324
|
-
host: "127.0.0.1",
|
|
398
|
+
host: options.proxyHost || "127.0.0.1",
|
|
325
399
|
port: 0
|
|
326
400
|
}
|
|
327
401
|
})
|