rollbridge 0.1.1
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/AI_TODO_PROMPT.md +39 -0
- package/README.md +200 -0
- package/TODO.md +96 -0
- package/bin/rollbridge +4 -0
- package/eslint.config.js +99 -0
- package/examples/tensorbuzz.com.js +85 -0
- package/package.json +32 -0
- package/scripts/release-patch.js +83 -0
- package/src/cli.js +359 -0
- package/src/config.js +414 -0
- package/src/control-client.js +42 -0
- package/src/daemon.js +575 -0
- package/src/health.js +31 -0
- package/src/json.d.ts +3 -0
- package/src/json.js +5 -0
- package/src/managed-process.js +232 -0
- package/src/port-allocator.js +88 -0
- package/src/release-group.js +287 -0
- package/src/template.js +74 -0
- package/tensorbuzz.yml +4 -0
- package/test/config-examples.test.js +59 -0
- package/test/config-path.test.js +90 -0
- package/test/config-validation.test.js +156 -0
- package/test/fixtures/dependent-app.js +57 -0
- package/test/fixtures/dummy-app.js +69 -0
- package/test/fixtures/service-app.js +42 -0
- package/test/fixtures/singleton-app.js +35 -0
- package/test/port-allocator.test.js +76 -0
- package/test/rollbridge.test.js +444 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# AI Prompt For Rollbridge TODO Work
|
|
2
|
+
|
|
3
|
+
Use this prompt when asking an AI agent to implement one or more isolated Rollbridge TODO items.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
You are working in the Rollbridge repository.
|
|
7
|
+
|
|
8
|
+
Goal:
|
|
9
|
+
Pick one or more isolated unchecked items from TODO.md and implement them end to end. Prefer one focused item unless several TODOs are tightly coupled and can be completed cleanly in the same change.
|
|
10
|
+
|
|
11
|
+
Before changing files:
|
|
12
|
+
1. Read README.md, TODO.md, package.json, and the source files related to the chosen TODO item.
|
|
13
|
+
2. State the exact TODO item(s) you are taking.
|
|
14
|
+
3. Confirm the item is isolated. Do not combine unrelated roadmap items.
|
|
15
|
+
|
|
16
|
+
Scope rules:
|
|
17
|
+
- Keep the diff focused on the selected TODO item(s).
|
|
18
|
+
- Do not add Capistrano plugins, Capistrano tasks, or Capistrano-specific integration code. Deploy tools should call Rollbridge through CLI commands.
|
|
19
|
+
- Capistrano documentation examples are allowed only as shell-command recipes that invoke Rollbridge CLI commands.
|
|
20
|
+
- Do not change runtime behavior for documentation-only TODOs.
|
|
21
|
+
- Do not mark a TODO checkbox complete unless the feature or document is actually finished and validated.
|
|
22
|
+
- If the work changes public config, CLI output, command names, or operational behavior, update README.md or dedicated docs in the same change.
|
|
23
|
+
|
|
24
|
+
Implementation expectations:
|
|
25
|
+
- Follow existing Rollbridge patterns and JSDoc style.
|
|
26
|
+
- Keep config validation at the boundary and trust normalized config downstream.
|
|
27
|
+
- Add behavior-focused tests for new process, daemon, CLI, config, or proxy behavior.
|
|
28
|
+
- For worker or memory supervision work, include status output and tests covering failure/restart behavior.
|
|
29
|
+
- For docs work, include concrete commands and examples that an operator can use.
|
|
30
|
+
|
|
31
|
+
Suggested acceptance criteria:
|
|
32
|
+
- The selected TODO item(s) are implemented or documented fully.
|
|
33
|
+
- Relevant TODO.md checkbox(es) are updated.
|
|
34
|
+
- npm run all-checks passes.
|
|
35
|
+
- The final response summarizes changed behavior, docs, tests, and any remaining follow-up work.
|
|
36
|
+
|
|
37
|
+
If the TODO item is too large:
|
|
38
|
+
Split it into a smaller independently shippable subtask, document the boundary in TODO.md, and implement only that subtask.
|
|
39
|
+
```
|
package/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Rollbridge
|
|
2
|
+
|
|
3
|
+
Rollbridge is a Node.js process supervisor and local traffic switcher for zero-downtime deploys.
|
|
4
|
+
|
|
5
|
+
Nginx points at one stable Rollbridge proxy port. Deploy tooling asks Rollbridge to start a new release, health-check it, switch new traffic to it, and drain old HTTP/WebSocket connections before stopping the previous release.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install rollbridge
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For local development in this repository:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
npm run all-checks
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Config
|
|
21
|
+
|
|
22
|
+
A Rollbridge config is a JavaScript module that `export default`s a config
|
|
23
|
+
object. It can also export a function (sync or async) that returns the object,
|
|
24
|
+
which is handy for computing values from the environment. Write it in your
|
|
25
|
+
project's module system — `export default` for ESM (`"type": "module"`) or
|
|
26
|
+
`module.exports` for CommonJS.
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
// rollbridge.js
|
|
30
|
+
export default {
|
|
31
|
+
application: "ticket-server",
|
|
32
|
+
|
|
33
|
+
control: {
|
|
34
|
+
path: "/tmp/rollbridge-ticket-server.sock"
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
proxy: {
|
|
38
|
+
host: "127.0.0.1",
|
|
39
|
+
port: 8182,
|
|
40
|
+
healthPath: "/ping",
|
|
41
|
+
healthTimeoutMs: 30000,
|
|
42
|
+
drainTimeoutMs: 60000,
|
|
43
|
+
forceStopTimeoutMs: 10000
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
processes: [
|
|
47
|
+
{
|
|
48
|
+
id: "beacon",
|
|
49
|
+
policy: "companion",
|
|
50
|
+
cwd: "{{releasePath}}",
|
|
51
|
+
command: "env VELOCIOUS_BEACON_PORT={{port}} npx velocious beacon",
|
|
52
|
+
port: {from: 17330, to: 17399}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "background-jobs-worker",
|
|
56
|
+
policy: "companion",
|
|
57
|
+
cwd: "{{releasePath}}",
|
|
58
|
+
command: "npx velocious background-jobs-worker"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "background-jobs-main",
|
|
62
|
+
policy: "service",
|
|
63
|
+
cwd: "{{releasePath}}",
|
|
64
|
+
command: "npx velocious background-jobs-main"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "web",
|
|
68
|
+
policy: "proxied",
|
|
69
|
+
cwd: "{{releasePath}}",
|
|
70
|
+
command: "npx velocious server --host 127.0.0.1 --port {{port}}",
|
|
71
|
+
port: {from: 18182, to: 18299},
|
|
72
|
+
health: {path: "/ping", timeoutMs: 30000}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A function export receives no arguments and lets you build the config at load
|
|
79
|
+
time:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
// rollbridge.js
|
|
83
|
+
export default () => ({
|
|
84
|
+
application: process.env.APP_NAME || "ticket-server",
|
|
85
|
+
control: {path: `/tmp/rollbridge-${process.env.APP_NAME || "ticket-server"}.sock`},
|
|
86
|
+
proxy: {host: "127.0.0.1", port: 8182},
|
|
87
|
+
processes: [
|
|
88
|
+
{id: "web", policy: "proxied", cwd: "{{releasePath}}", command: "npx velocious server --port {{port}}", port: {from: 18182, to: 18299}}
|
|
89
|
+
]
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Production-ready examples live in `examples/`, including
|
|
94
|
+
`examples/tensorbuzz.com.js` for the current TensorBuzz backend deployment.
|
|
95
|
+
|
|
96
|
+
## Process Policies
|
|
97
|
+
|
|
98
|
+
- `proxied`: the web/API process. Rollbridge forwards HTTP and WebSocket traffic to the active release and tracks connections for draining.
|
|
99
|
+
- `companion`: a release-scoped support process. It starts with the release and stops after that release drains.
|
|
100
|
+
- `singleton`: a one-at-a-time support process. Rollbridge stops the old singleton before starting the new one, so duplicate-unsafe schedulers or job dispatchers do not overlap.
|
|
101
|
+
- `service`: a daemon-wide support process. Rollbridge starts it before release processes need it, leaves it running across deploys, and updates its restart template after a successful deploy.
|
|
102
|
+
|
|
103
|
+
## Commands
|
|
104
|
+
|
|
105
|
+
`--config` is optional for every command. When omitted, Rollbridge looks for
|
|
106
|
+
`rollbridge.js` in the current directory. The examples below pass `--config`
|
|
107
|
+
explicitly, but `rollbridge validate` (or any command) works with no flag when a
|
|
108
|
+
`rollbridge.js` is present.
|
|
109
|
+
|
|
110
|
+
Validate a config without starting the daemon:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
rollbridge validate --config rollbridge.js
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`validate` reports every config error at once with an example fix and exits
|
|
117
|
+
non-zero when issues are found, so deploy tooling can gate on it. It checks
|
|
118
|
+
required fields and types, duplicate process IDs, port ranges, that exactly one
|
|
119
|
+
process is `proxied`, and that the proxied process defines a port range. Example
|
|
120
|
+
output for a misconfigured file:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
Found 2 configuration issues in rollbridge.js:
|
|
124
|
+
|
|
125
|
+
1. Config must define exactly one proxied process; found 0
|
|
126
|
+
Fix: Mark exactly one process with policy: proxied so Rollbridge knows where to forward traffic.
|
|
127
|
+
|
|
128
|
+
2. Duplicate process id: web
|
|
129
|
+
Fix: Give each process a unique id; "web" is used more than once.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Start the daemon:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
rollbridge daemon --config rollbridge.js
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Start the daemon only when it is not already running:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
rollbridge ensure-daemon --config rollbridge.js --daemon-log-path log/rollbridge.log --daemon-pid-path tmp/pids/rollbridge.pid
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Deploy a prepared release:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
rollbridge deploy --config rollbridge.js --release-path /home/dev/ticket-server/releases/20260521073000/ticket-server --revision abc123
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Deploy and start the daemon first when needed:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
rollbridge deploy --ensure-daemon --config rollbridge.js --release-path /home/dev/ticket-server/releases/20260521073000/ticket-server --revision abc123
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Inspect state:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
rollbridge status --config rollbridge.js
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Stop the active release:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
rollbridge stop --config rollbridge.js
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Shut down the daemon and managed processes:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
rollbridge shutdown --config rollbridge.js
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Nginx
|
|
175
|
+
|
|
176
|
+
Nginx should proxy to Rollbridge, not directly to Velocious:
|
|
177
|
+
|
|
178
|
+
```nginx
|
|
179
|
+
location / {
|
|
180
|
+
proxy_pass http://127.0.0.1:8182;
|
|
181
|
+
proxy_http_version 1.1;
|
|
182
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
183
|
+
proxy_set_header Connection "upgrade";
|
|
184
|
+
proxy_set_header Host $host;
|
|
185
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
186
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Deployment Notes
|
|
191
|
+
|
|
192
|
+
Run migrations before `rollbridge deploy`, and keep migrations backwards-compatible while old and new web releases overlap. For stable local brokers such as Velocious Beacon or `background-jobs-main`, use `service` when the process should survive deploys and restart from the latest successful release if it crashes.
|
|
193
|
+
|
|
194
|
+
## Releasing
|
|
195
|
+
|
|
196
|
+
Maintainers can publish a patch release from the latest default branch:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
npm run release:patch
|
|
200
|
+
```
|
package/TODO.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Rollbridge TODO
|
|
2
|
+
|
|
3
|
+
This roadmap tracks planned Rollbridge features and documentation. Rollbridge should stay deploy-tool agnostic: Capistrano, shell scripts, CI, or other deploy systems should call Rollbridge through CLI commands instead of Rollbridge shipping Capistrano tasks or plugins.
|
|
4
|
+
|
|
5
|
+
## Current Baseline
|
|
6
|
+
|
|
7
|
+
- [x] Stable local HTTP/WebSocket proxy in front of one active release.
|
|
8
|
+
- [x] Unix socket control API used by the CLI.
|
|
9
|
+
- [x] `daemon`, `ensure-daemon`, `deploy`, `status`, `stop`, and `shutdown` commands.
|
|
10
|
+
- [x] Per-release process startup with templated `releasePath`, `releaseId`, `revision`, and ports.
|
|
11
|
+
- [x] Process policies for `proxied`, `companion`, `singleton`, and `service`.
|
|
12
|
+
- [x] HTTP health check before switching traffic to a new web process.
|
|
13
|
+
- [x] Drain old HTTP/WebSocket connections before stopping previous release processes.
|
|
14
|
+
- [x] Restart crashed active release processes after `restartDelayMs`.
|
|
15
|
+
- [x] Restart crashed daemon-wide service processes from the latest successful release template.
|
|
16
|
+
- [x] Graceful `SIGTERM` followed by `SIGKILL` after timeout.
|
|
17
|
+
- [x] TensorBuzz production example config.
|
|
18
|
+
- [x] ESLint, TypeScript `checkJs`, TensorBuzz CI, and `release:patch`.
|
|
19
|
+
|
|
20
|
+
## Major Features
|
|
21
|
+
|
|
22
|
+
- [ ] Memory supervision.
|
|
23
|
+
- [ ] Add per-process memory config with an RSS limit, check interval, warning threshold, and restart policy.
|
|
24
|
+
- [ ] Measure the managed process tree, not only the shell wrapper PID.
|
|
25
|
+
- [ ] Report memory stats and last memory-triggered restart in `status`.
|
|
26
|
+
- [ ] Restart memory-heavy workers gracefully when possible, with a forced stop timeout.
|
|
27
|
+
- [ ] Add tests with a fixture process that allocates memory above the configured limit.
|
|
28
|
+
- [ ] Worker auto-restart and restart policy controls.
|
|
29
|
+
- [ ] Add config for max restarts, restart window, exponential backoff, and disabled restart behavior.
|
|
30
|
+
- [ ] Distinguish crash restarts, deploy replacements, manual restarts, and memory restarts in status/events.
|
|
31
|
+
- [ ] Add a `restart` CLI command for a single process, a policy group, or all non-proxied workers.
|
|
32
|
+
- [ ] Keep restart behavior safe for job workers by using lifecycle hooks before termination.
|
|
33
|
+
- [ ] Graceful job-worker lifecycle.
|
|
34
|
+
- [ ] Add generic lifecycle hooks such as `quietCommand`, `drainCommand`, `drainTimeoutMs`, and `stopCommand`.
|
|
35
|
+
- [ ] Support signal-only lifecycle steps for workers that can quiet on a Unix signal.
|
|
36
|
+
- [ ] Add a non-blocking drain mode so new workers can start while old workers finish running jobs.
|
|
37
|
+
- [ ] Document a Velocious background-jobs-worker recipe once the lifecycle contract is implemented.
|
|
38
|
+
- [ ] Replicas and stable worker indexes.
|
|
39
|
+
- [ ] Allow one process config to start multiple replicas.
|
|
40
|
+
- [ ] Expose `ROLLBRIDGE_REPLICA_INDEX`, replica count, and per-replica template context.
|
|
41
|
+
- [ ] Restart or stop one replica without affecting the rest.
|
|
42
|
+
- [ ] Preserve readable status output for replica groups.
|
|
43
|
+
- [ ] Persistent daemon state and recovery.
|
|
44
|
+
- [ ] Persist active release, draining releases, process metadata, counters, and recent events.
|
|
45
|
+
- [ ] Reconnect status to still-running child processes after daemon restart where possible.
|
|
46
|
+
- [ ] Detect and report orphaned Rollbridge-managed processes.
|
|
47
|
+
- [ ] Add a recovery mode for safe startup after daemon crash or machine reboot.
|
|
48
|
+
- [ ] Rollback support.
|
|
49
|
+
- [ ] Keep enough release metadata to switch traffic back to a previous healthy release.
|
|
50
|
+
- [ ] Add a `rollback` CLI command that health-checks the target before switching.
|
|
51
|
+
- [ ] Define how rollback interacts with singleton workers and draining releases.
|
|
52
|
+
- [ ] Document migration constraints for rollback.
|
|
53
|
+
- [ ] Observability and diagnostics.
|
|
54
|
+
- [ ] Add structured event history for deploys, switches, stops, crashes, memory restarts, and failed commands.
|
|
55
|
+
- [ ] Add restart counters, uptime, exit reasons, memory stats, and child-process-tree details to status.
|
|
56
|
+
- [ ] Add `logs` and `events` CLI commands.
|
|
57
|
+
- [ ] Add optional file logging with rotation guidance.
|
|
58
|
+
- [ ] Add machine-readable JSON output flags for all CLI commands.
|
|
59
|
+
- [ ] Config validation and doctoring.
|
|
60
|
+
- [x] Add `validate` to parse config and report all config errors without starting the daemon.
|
|
61
|
+
- [ ] Add `doctor` to check control socket reachability, proxy port availability, process commands, release path, and writable log/state paths.
|
|
62
|
+
- [x] Validate duplicate process IDs, missing ports on proxied processes, invalid ranges, and the single-proxied-process policy rule.
|
|
63
|
+
- [ ] Validate unsupported lifecycle-hook combinations once worker lifecycle hooks land.
|
|
64
|
+
- [x] Include example fixes in validation output.
|
|
65
|
+
|
|
66
|
+
## Minor Features
|
|
67
|
+
|
|
68
|
+
- [ ] Add socket permission and socket owner/group options for shared deploy users.
|
|
69
|
+
- [x] Make stale control socket diagnostics clearer when another daemon is still alive.
|
|
70
|
+
- [ ] Add old-release cleanup policies by age, count, and stopped state.
|
|
71
|
+
- [x] Add port allocation diagnostics when a range is exhausted.
|
|
72
|
+
- [ ] Add optional startup command timeout before health checks begin.
|
|
73
|
+
- [ ] Add process output retention config instead of a fixed recent-log count.
|
|
74
|
+
- [ ] Add environment variable interpolation from the daemon environment.
|
|
75
|
+
- [x] Add `--config` default lookup resolving to `rollbridge.js` when no path is given.
|
|
76
|
+
- [ ] Add shell completion generation for common shells.
|
|
77
|
+
- [ ] Add npm package metadata such as repository, license, bugs, and homepage.
|
|
78
|
+
- [ ] Add systemd service examples for the Rollbridge daemon.
|
|
79
|
+
- [ ] Add tests for malformed control socket JSON and unknown control commands.
|
|
80
|
+
- [ ] Add tests for duplicate IDs and singleton replacement failure behavior.
|
|
81
|
+
- [ ] Add tests for proxy behavior when the active release exits unexpectedly.
|
|
82
|
+
|
|
83
|
+
## Documentation TODO
|
|
84
|
+
|
|
85
|
+
- [ ] Write a full config reference covering every field, default, and template variable.
|
|
86
|
+
- [ ] Write a CLI reference for `daemon`, `ensure-daemon`, `deploy`, `status`, `stop`, `shutdown`, and future commands.
|
|
87
|
+
- [ ] Expand process policy docs with deployment examples for `proxied`, `companion`, `singleton`, and `service`.
|
|
88
|
+
- [ ] Document memory checks and auto-restart behavior after the feature lands.
|
|
89
|
+
- [ ] Document worker lifecycle hooks and safe background-job deployment patterns after the feature lands.
|
|
90
|
+
- [ ] Add a Velocious deployment guide with Beacon, background-jobs-main, background-jobs-worker, and web process examples.
|
|
91
|
+
- [ ] Add an Nginx guide with WebSocket headers, timeouts, and common failure modes.
|
|
92
|
+
- [ ] Add deploy-tool recipes that call Rollbridge CLI commands directly.
|
|
93
|
+
- [ ] Add a Capistrano recipe showing shell commands only; do not add a Capistrano plugin or Rollbridge-specific Capistrano tasks.
|
|
94
|
+
- [ ] Add a TensorBuzz-specific runbook for current production ports, external services, deploy ordering, and rollback constraints.
|
|
95
|
+
- [ ] Add troubleshooting docs for health-check failures, port conflicts, stale sockets, crash loops, and stuck draining releases.
|
|
96
|
+
- [ ] Add a release checklist for maintainers using `npm run release:patch`.
|
package/bin/rollbridge
ADDED
package/eslint.config.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import js from "@eslint/js"
|
|
2
|
+
import {jsdoc} from 'eslint-plugin-jsdoc'
|
|
3
|
+
import globals from "globals"
|
|
4
|
+
import { defineConfig } from "eslint/config"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extracts the brace-balanced type expressions that follow JSDoc tags in a comment body.
|
|
8
|
+
* @param {string} commentValue The raw comment text between the comment delimiters.
|
|
9
|
+
* @returns {string[]} Each `{...}` type expression, without its surrounding braces.
|
|
10
|
+
*/
|
|
11
|
+
function jsdocTypeExpressions(commentValue) {
|
|
12
|
+
const types = []
|
|
13
|
+
const tagWithType = /@\w+\s*\{/g
|
|
14
|
+
let match
|
|
15
|
+
|
|
16
|
+
while ((match = tagWithType.exec(commentValue))) {
|
|
17
|
+
let depth = 0
|
|
18
|
+
let start = -1
|
|
19
|
+
|
|
20
|
+
for (let index = match.index + match[0].length - 1; index < commentValue.length; index++) {
|
|
21
|
+
const character = commentValue[index]
|
|
22
|
+
|
|
23
|
+
if (character === "{") {
|
|
24
|
+
if (depth === 0) start = index
|
|
25
|
+
depth++
|
|
26
|
+
} else if (character === "}") {
|
|
27
|
+
depth--
|
|
28
|
+
|
|
29
|
+
if (depth === 0) {
|
|
30
|
+
types.push(commentValue.slice(start + 1, index))
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return types
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// eslint-plugin-jsdoc rejects `any` everywhere (jsdoc/reject-any-type) but only catches `unknown`
|
|
41
|
+
// at the top level, so this local rule rejects `unknown` anywhere inside a JSDoc type expression.
|
|
42
|
+
const localPlugin = {
|
|
43
|
+
rules: {
|
|
44
|
+
"no-unknown-jsdoc-type": {
|
|
45
|
+
meta: {
|
|
46
|
+
type: "problem",
|
|
47
|
+
docs: {description: "Disallow the `unknown` type anywhere in JSDoc type annotations."},
|
|
48
|
+
messages: {noUnknown: "Avoid the unknown type in JSDoc; use a specific type such as JsonValue (src/json.d.ts) instead."},
|
|
49
|
+
schema: []
|
|
50
|
+
},
|
|
51
|
+
create(context) {
|
|
52
|
+
return {
|
|
53
|
+
Program() {
|
|
54
|
+
for (const comment of context.sourceCode.getAllComments()) {
|
|
55
|
+
if (comment.type !== "Block" || !comment.value.startsWith("*")) continue
|
|
56
|
+
|
|
57
|
+
const hasUnknown = jsdocTypeExpressions(comment.value).some((type) => /\bunknown\b/.test(type))
|
|
58
|
+
|
|
59
|
+
if (hasUnknown) context.report({loc: comment.loc, messageId: "noUnknown"})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default defineConfig([
|
|
69
|
+
{
|
|
70
|
+
name: "global ignores",
|
|
71
|
+
ignores: ["build/**", "dist/**"],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
files: ["**/*.{js,mjs,cjs}", "**/bin/rollbridge"],
|
|
75
|
+
plugins: {js},
|
|
76
|
+
extends: ["js/recommended"],
|
|
77
|
+
languageOptions: {
|
|
78
|
+
globals: {...globals.browser, ...globals.node}
|
|
79
|
+
},
|
|
80
|
+
rules: {
|
|
81
|
+
"no-unused-vars": ["error", {argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_"}]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
jsdoc({
|
|
85
|
+
config: "flat/recommended",
|
|
86
|
+
files: ["**/*.{js,mjs,cjs}", "**/bin/rollbridge"],
|
|
87
|
+
rules: {
|
|
88
|
+
"jsdoc/reject-any-type": "error"
|
|
89
|
+
}
|
|
90
|
+
}),
|
|
91
|
+
{
|
|
92
|
+
name: "local jsdoc type rules",
|
|
93
|
+
files: ["**/*.{js,mjs,cjs}", "**/bin/rollbridge"],
|
|
94
|
+
plugins: {local: localPlugin},
|
|
95
|
+
rules: {
|
|
96
|
+
"local/no-unknown-jsdoc-type": "error"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
])
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Rollbridge config for the TensorBuzz production backend.
|
|
2
|
+
//
|
|
3
|
+
// Nginx should keep proxying the backend host to 127.0.0.1:4500. Rollbridge
|
|
4
|
+
// binds that stable HTTP port, forwards to the active release's internal web
|
|
5
|
+
// port, keeps Beacon/jobs-main as daemon-wide services, and starts one
|
|
6
|
+
// background worker per active release.
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
application: "tensorbuzz",
|
|
10
|
+
|
|
11
|
+
control: {
|
|
12
|
+
path: "/tmp/rollbridge-tensorbuzz.sock"
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
proxy: {
|
|
16
|
+
host: "127.0.0.1",
|
|
17
|
+
port: 4500,
|
|
18
|
+
healthPath: "/ping",
|
|
19
|
+
healthTimeoutMs: 30000,
|
|
20
|
+
drainTimeoutMs: 60000,
|
|
21
|
+
forceStopTimeoutMs: 10000
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
processes: [
|
|
25
|
+
{
|
|
26
|
+
id: "beacon",
|
|
27
|
+
policy: "service",
|
|
28
|
+
cwd: "{{releasePath}}/backend",
|
|
29
|
+
env: {
|
|
30
|
+
NODE_ENV: "production",
|
|
31
|
+
VELOCIOUS_ENV: "production",
|
|
32
|
+
VELOCIOUS_BEACON_PORT: "{{port}}"
|
|
33
|
+
},
|
|
34
|
+
command: "npx velocious beacon",
|
|
35
|
+
port: 7330
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "background-jobs-main",
|
|
39
|
+
policy: "service",
|
|
40
|
+
cwd: "{{releasePath}}/backend",
|
|
41
|
+
env: {
|
|
42
|
+
NODE_ENV: "production",
|
|
43
|
+
VELOCIOUS_ENV: "production",
|
|
44
|
+
VELOCIOUS_BEACON_PORT: "{{ports.beacon}}",
|
|
45
|
+
VELOCIOUS_BACKGROUND_JOBS_PORT: "{{port}}"
|
|
46
|
+
},
|
|
47
|
+
command: "wait-for-it 127.0.0.1:{{ports.beacon}} --strict -- npx velocious background-jobs-main",
|
|
48
|
+
port: 7331
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "background-jobs-worker",
|
|
52
|
+
policy: "companion",
|
|
53
|
+
cwd: "{{releasePath}}/backend",
|
|
54
|
+
env: {
|
|
55
|
+
NODE_ENV: "production",
|
|
56
|
+
VELOCIOUS_ENV: "production",
|
|
57
|
+
VELOCIOUS_BEACON_PORT: "{{ports.beacon}}",
|
|
58
|
+
VELOCIOUS_BACKGROUND_JOBS_PORT: "{{ports.background-jobs-main}}"
|
|
59
|
+
},
|
|
60
|
+
command: "wait-for-it 127.0.0.1:{{ports.beacon}} --strict -- wait-for-it 127.0.0.1:{{ports.background-jobs-main}} --strict -- npx velocious background-jobs-worker",
|
|
61
|
+
gracefulStopMs: 60000
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "web",
|
|
65
|
+
policy: "proxied",
|
|
66
|
+
cwd: "{{releasePath}}/backend",
|
|
67
|
+
env: {
|
|
68
|
+
NODE_ENV: "production",
|
|
69
|
+
VELOCIOUS_ENV: "production",
|
|
70
|
+
VELOCIOUS_BEACON_PORT: "{{ports.beacon}}",
|
|
71
|
+
VELOCIOUS_BACKGROUND_JOBS_PORT: "{{ports.background-jobs-main}}"
|
|
72
|
+
},
|
|
73
|
+
command: "wait-for-it 127.0.0.1:{{ports.beacon}} --strict -- wait-for-it 127.0.0.1:{{ports.background-jobs-main}} --strict -- npx velocious server --host 127.0.0.1 --port {{port}}",
|
|
74
|
+
port: {
|
|
75
|
+
from: 14500,
|
|
76
|
+
to: 14599
|
|
77
|
+
},
|
|
78
|
+
health: {
|
|
79
|
+
path: "/ping",
|
|
80
|
+
timeoutMs: 30000,
|
|
81
|
+
intervalMs: 500
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rollbridge",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Zero-downtime process supervisor and local traffic switcher for deploy-managed apps.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rollbridge": "./bin/rollbridge"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"all-checks": "npm run typecheck && npm run lint && npm test",
|
|
11
|
+
"lint": "eslint",
|
|
12
|
+
"release:patch": "node scripts/release-patch.js",
|
|
13
|
+
"test": "node --test test/*.test.js",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^12.1.0",
|
|
21
|
+
"http-proxy": "^1.18.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@eslint/js": "^10.0.1",
|
|
25
|
+
"@types/http-proxy": "^1.17.17",
|
|
26
|
+
"@types/node": "^25.9.1",
|
|
27
|
+
"eslint": "^10.4.0",
|
|
28
|
+
"eslint-plugin-jsdoc": "^62.9.0",
|
|
29
|
+
"globals": "^17.6.0",
|
|
30
|
+
"typescript": "^6.0.3"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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"])
|