rollbridge 0.1.4 → 0.1.6
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 +137 -4
- package/TODO.md +47 -45
- package/docs/cli.md +169 -6
- package/docs/config.md +160 -3
- package/docs/logging.md +77 -0
- package/docs/nginx.md +104 -0
- package/docs/releasing.md +53 -0
- package/docs/tensorbuzz-runbook.md +129 -0
- package/docs/velocious.md +238 -0
- package/docs/workers.md +115 -0
- package/package.json +3 -2
- package/src/cli.js +317 -1
- package/src/config.js +240 -6
- package/src/daemon.js +284 -4
- package/src/doctor.js +177 -0
- package/src/event-log.js +47 -0
- package/src/managed-process.js +287 -22
- package/src/process-memory.js +110 -0
- package/src/recover.js +134 -0
- package/src/release-group.js +80 -21
- package/src/state-store.js +103 -0
- package/src/system-ids.js +71 -0
- package/src/template.js +32 -0
- package/test/completion.test.js +64 -0
- package/test/config-validation.test.js +267 -0
- package/test/doctor.test.js +205 -3
- package/test/event-log.test.js +46 -0
- package/test/fixtures/memory-hog.js +19 -0
- package/test/managed-process.test.js +376 -0
- package/test/process-memory.test.js +40 -0
- package/test/recover.test.js +162 -0
- package/test/release-group.test.js +22 -0
- package/test/rollbridge.test.js +716 -6
- package/test/state-store.test.js +69 -0
- package/test/system-ids.test.js +24 -0
- package/scripts/release-patch.js +0 -83
|
@@ -0,0 +1,69 @@
|
|
|
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 {clearState, readState, writeState} from "../src/state-store.js"
|
|
9
|
+
|
|
10
|
+
test("writeState then readState round-trips a snapshot", async () => {
|
|
11
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-state-"))
|
|
12
|
+
const statePath = path.join(dir, "state.json")
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await writeState(statePath, {activeReleaseId: "v1", releases: [{releaseId: "v1"}]})
|
|
16
|
+
|
|
17
|
+
const state = /** @type {{activeReleaseId: string}} */ (await readState(statePath))
|
|
18
|
+
|
|
19
|
+
assert.equal(state.activeReleaseId, "v1")
|
|
20
|
+
} finally {
|
|
21
|
+
await fs.rm(dir, {force: true, recursive: true})
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("readState returns undefined for a missing or unparseable file", async () => {
|
|
26
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-state-"))
|
|
27
|
+
const statePath = path.join(dir, "state.json")
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
assert.equal(await readState(statePath), undefined)
|
|
31
|
+
|
|
32
|
+
await fs.writeFile(statePath, "{not json")
|
|
33
|
+
|
|
34
|
+
assert.equal(await readState(statePath), undefined)
|
|
35
|
+
} finally {
|
|
36
|
+
await fs.rm(dir, {force: true, recursive: true})
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("concurrent writes leave a complete, uncorrupted snapshot", async () => {
|
|
41
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-state-"))
|
|
42
|
+
const statePath = path.join(dir, "state.json")
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await Promise.all([writeState(statePath, {n: 1}), writeState(statePath, {n: 2}), writeState(statePath, {n: 3})])
|
|
46
|
+
|
|
47
|
+
const state = /** @type {{n: number}} */ (await readState(statePath))
|
|
48
|
+
|
|
49
|
+
// A complete snapshot from one of the writers — never a partial/corrupt file or a temp race.
|
|
50
|
+
assert.ok(state && typeof state.n === "number")
|
|
51
|
+
} finally {
|
|
52
|
+
await fs.rm(dir, {force: true, recursive: true})
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("clearState removes the file and ignores a missing one", async () => {
|
|
57
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-state-"))
|
|
58
|
+
const statePath = path.join(dir, "state.json")
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await writeState(statePath, {ok: true})
|
|
62
|
+
await clearState(statePath)
|
|
63
|
+
|
|
64
|
+
assert.equal(await readState(statePath), undefined)
|
|
65
|
+
await clearState(statePath)
|
|
66
|
+
} finally {
|
|
67
|
+
await fs.rm(dir, {force: true, recursive: true})
|
|
68
|
+
}
|
|
69
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict"
|
|
4
|
+
import test from "node:test"
|
|
5
|
+
import {resolveGroupId, resolveUserId} from "../src/system-ids.js"
|
|
6
|
+
|
|
7
|
+
const linuxOnly = process.platform !== "linux" && "requires /etc/passwd and /etc/group (Linux)"
|
|
8
|
+
|
|
9
|
+
test("resolves numeric ids and numeric strings as-is", () => {
|
|
10
|
+
assert.equal(resolveUserId(1000), 1000)
|
|
11
|
+
assert.equal(resolveUserId("1000"), 1000)
|
|
12
|
+
assert.equal(resolveGroupId(0), 0)
|
|
13
|
+
assert.equal(resolveGroupId("42"), 42)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test("resolves user and group names to ids", {skip: linuxOnly}, () => {
|
|
17
|
+
assert.equal(resolveUserId("root"), 0)
|
|
18
|
+
assert.equal(resolveGroupId("root"), 0)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test("throws for an unknown user or group name", {skip: linuxOnly}, () => {
|
|
22
|
+
assert.throws(() => resolveUserId("rollbridge-no-such-user"), /Unknown user "rollbridge-no-such-user"/)
|
|
23
|
+
assert.throws(() => resolveGroupId("rollbridge-no-such-group"), /Unknown group "rollbridge-no-such-group"/)
|
|
24
|
+
})
|
package/scripts/release-patch.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {execFileSync} from "node:child_process"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Runs a command and inherits stdio.
|
|
6
|
-
* @param {string} command - Command to run.
|
|
7
|
-
* @param {string[]} [args] - Command arguments.
|
|
8
|
-
* @returns {void}
|
|
9
|
-
*/
|
|
10
|
-
function run(command, args = []) {
|
|
11
|
-
execFileSync(command, args, {
|
|
12
|
-
env: {
|
|
13
|
-
...process.env,
|
|
14
|
-
GIT_EDITOR: "true",
|
|
15
|
-
GIT_MERGE_AUTOEDIT: "no"
|
|
16
|
-
},
|
|
17
|
-
stdio: "inherit"
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Runs a command and returns trimmed stdout.
|
|
23
|
-
* @param {string} command - Command to run.
|
|
24
|
-
* @param {string[]} [args] - Command arguments.
|
|
25
|
-
* @returns {string} Trimmed stdout.
|
|
26
|
-
*/
|
|
27
|
-
function output(command, args = []) {
|
|
28
|
-
return execFileSync(command, args, {encoding: "utf8"}).trim()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** @returns {string} GitHub remote default branch name. */
|
|
32
|
-
function defaultBranch() {
|
|
33
|
-
const remoteHead = output("git", ["ls-remote", "--symref", "origin", "HEAD"])
|
|
34
|
-
const match = remoteHead.match(/^ref: refs\/heads\/(.+)\s+HEAD$/m)
|
|
35
|
-
|
|
36
|
-
if (!match) throw new Error("Unable to determine origin default branch")
|
|
37
|
-
|
|
38
|
-
return match[1]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @param {string} branch - Branch name.
|
|
43
|
-
* @returns {boolean} True when the local branch exists.
|
|
44
|
-
*/
|
|
45
|
-
function localBranchExists(branch) {
|
|
46
|
-
try {
|
|
47
|
-
output("git", ["rev-parse", "--verify", `refs/heads/${branch}`])
|
|
48
|
-
return true
|
|
49
|
-
} catch (_error) {
|
|
50
|
-
return false
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** @returns {string} Updated default branch name. */
|
|
55
|
-
function updateLocalDefaultBranch() {
|
|
56
|
-
run("git", ["fetch", "origin"])
|
|
57
|
-
const branch = defaultBranch()
|
|
58
|
-
|
|
59
|
-
if (localBranchExists(branch)) {
|
|
60
|
-
run("git", ["checkout", branch])
|
|
61
|
-
} else {
|
|
62
|
-
run("git", ["checkout", "-b", branch, `origin/${branch}`])
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
run("git", ["merge", "--ff-only", `origin/${branch}`])
|
|
66
|
-
|
|
67
|
-
return branch
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
execFileSync("npm", ["whoami"], {stdio: "ignore"})
|
|
72
|
-
} catch {
|
|
73
|
-
run("npm", ["login"])
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const branch = updateLocalDefaultBranch()
|
|
77
|
-
|
|
78
|
-
run("npm", ["version", "patch", "--no-git-tag-version"])
|
|
79
|
-
run("npm", ["install"])
|
|
80
|
-
run("git", ["add", "package.json", "package-lock.json"])
|
|
81
|
-
run("git", ["commit", "-m", "chore: bump patch version"])
|
|
82
|
-
run("git", ["push", "origin", branch])
|
|
83
|
-
run("npm", ["publish"])
|