patchrelay 0.1.0
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 +271 -0
- package/config/patchrelay.example.json +5 -0
- package/dist/build-info.js +29 -0
- package/dist/build-info.json +6 -0
- package/dist/cli/data.js +461 -0
- package/dist/cli/formatters/json.js +3 -0
- package/dist/cli/formatters/text.js +119 -0
- package/dist/cli/index.js +761 -0
- package/dist/codex-app-server.js +353 -0
- package/dist/codex-types.js +1 -0
- package/dist/config-types.js +1 -0
- package/dist/config.js +494 -0
- package/dist/db/authoritative-ledger-store.js +437 -0
- package/dist/db/issue-workflow-store.js +690 -0
- package/dist/db/linear-installation-store.js +184 -0
- package/dist/db/migrations.js +183 -0
- package/dist/db/shared.js +101 -0
- package/dist/db/stage-event-store.js +33 -0
- package/dist/db/webhook-event-store.js +46 -0
- package/dist/db-ports.js +5 -0
- package/dist/db-types.js +1 -0
- package/dist/db.js +40 -0
- package/dist/file-permissions.js +40 -0
- package/dist/http.js +321 -0
- package/dist/index.js +69 -0
- package/dist/install.js +302 -0
- package/dist/installation-ports.js +1 -0
- package/dist/issue-query-service.js +68 -0
- package/dist/ledger-ports.js +1 -0
- package/dist/linear-client.js +338 -0
- package/dist/linear-oauth-service.js +131 -0
- package/dist/linear-oauth.js +154 -0
- package/dist/linear-types.js +1 -0
- package/dist/linear-workflow.js +78 -0
- package/dist/logging.js +62 -0
- package/dist/preflight.js +227 -0
- package/dist/project-resolution.js +51 -0
- package/dist/reconciliation-action-applier.js +55 -0
- package/dist/reconciliation-actions.js +1 -0
- package/dist/reconciliation-engine.js +312 -0
- package/dist/reconciliation-snapshot-builder.js +96 -0
- package/dist/reconciliation-types.js +1 -0
- package/dist/runtime-paths.js +89 -0
- package/dist/service-queue.js +49 -0
- package/dist/service-runtime.js +96 -0
- package/dist/service-stage-finalizer.js +348 -0
- package/dist/service-stage-runner.js +233 -0
- package/dist/service-webhook-processor.js +181 -0
- package/dist/service-webhooks.js +148 -0
- package/dist/service.js +139 -0
- package/dist/stage-agent-activity-publisher.js +33 -0
- package/dist/stage-event-ports.js +1 -0
- package/dist/stage-failure.js +92 -0
- package/dist/stage-launch.js +54 -0
- package/dist/stage-lifecycle-publisher.js +213 -0
- package/dist/stage-reporting.js +153 -0
- package/dist/stage-turn-input-dispatcher.js +102 -0
- package/dist/token-crypto.js +21 -0
- package/dist/types.js +5 -0
- package/dist/utils.js +163 -0
- package/dist/webhook-agent-session-handler.js +157 -0
- package/dist/webhook-archive.js +24 -0
- package/dist/webhook-comment-handler.js +89 -0
- package/dist/webhook-desired-stage-recorder.js +150 -0
- package/dist/webhook-event-ports.js +1 -0
- package/dist/webhook-installation-handler.js +57 -0
- package/dist/webhooks.js +301 -0
- package/dist/workflow-policy.js +42 -0
- package/dist/workflow-ports.js +1 -0
- package/dist/workflow-types.js +1 -0
- package/dist/worktree-manager.js +66 -0
- package/infra/patchrelay-reload.service +6 -0
- package/infra/patchrelay.path +11 -0
- package/infra/patchrelay.service +28 -0
- package/package.json +55 -0
- package/runtime.env.example +8 -0
- package/service.env.example +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PatchRelay contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# PatchRelay
|
|
2
|
+
|
|
3
|
+
PatchRelay is a self-hosted harness for Linear-driven Codex work on your own machine.
|
|
4
|
+
|
|
5
|
+
It receives Linear webhooks, routes issues to the right local repository, prepares durable issue worktrees, runs staged Codex sessions through `codex app-server`, and keeps the whole run observable and resumable from the CLI.
|
|
6
|
+
|
|
7
|
+
PatchRelay is the system around the model:
|
|
8
|
+
|
|
9
|
+
- webhook intake and verification
|
|
10
|
+
- Linear OAuth and workspace installations
|
|
11
|
+
- issue-to-repo routing
|
|
12
|
+
- issue worktree and branch lifecycle
|
|
13
|
+
- stage orchestration and thread continuity
|
|
14
|
+
- comment forwarding into active runs
|
|
15
|
+
- read-only inspection and stage reporting
|
|
16
|
+
|
|
17
|
+
If you want Codex to work inside your real repos with your real tools, secrets, SSH access, and deployment surface, PatchRelay is the harness that makes that loop reliable.
|
|
18
|
+
|
|
19
|
+
## Why PatchRelay
|
|
20
|
+
|
|
21
|
+
- Keep the agent in the real environment instead of rebuilding that environment in a hosted sandbox.
|
|
22
|
+
- Use your existing machine, repos, secrets, SSH config, shell tools, and deployment access.
|
|
23
|
+
- Keep deterministic workflow logic outside the model: routing, staging, worktree ownership, and reporting.
|
|
24
|
+
- Choose the Codex approval and sandbox settings that match your risk tolerance.
|
|
25
|
+
- Let Linear drive the loop through delegation, workflow stages, and comments.
|
|
26
|
+
- Drop into the exact issue worktree and resume control manually when needed.
|
|
27
|
+
|
|
28
|
+
## What PatchRelay Owns
|
|
29
|
+
|
|
30
|
+
PatchRelay does the deterministic harness work that you do not want to re-implement around every model run:
|
|
31
|
+
|
|
32
|
+
- verifies and deduplicates Linear webhooks
|
|
33
|
+
- maps issue events to the correct local project and workflow policy
|
|
34
|
+
- creates and reuses one durable worktree and branch per issue lifecycle
|
|
35
|
+
- starts or forks Codex threads for the workflows you bind to Linear states
|
|
36
|
+
- persists enough state to correlate the Linear issue, local workspace, stage run, and Codex thread
|
|
37
|
+
- reports progress back to Linear and forwards follow-up comments into active runs
|
|
38
|
+
- exposes CLI and optional read-only inspection surfaces so operators can understand what happened
|
|
39
|
+
|
|
40
|
+
## System Layers
|
|
41
|
+
|
|
42
|
+
PatchRelay works best when read as five layers with clear ownership:
|
|
43
|
+
|
|
44
|
+
- policy layer: repo workflow files and stage prompts
|
|
45
|
+
- coordination layer: issue claiming, stage selection, retries, and reconciliation
|
|
46
|
+
- execution layer: durable worktrees, Codex threads, and queued turn input delivery
|
|
47
|
+
- integration layer: Linear webhooks, OAuth, project routing, and deterministic state sync
|
|
48
|
+
- observability layer: CLI inspection, reports, event trails, and operator endpoints
|
|
49
|
+
|
|
50
|
+
That separation is intentional. PatchRelay is not the policy itself and it is not the coding agent. It is the harness that keeps those pieces coordinated in a real repository with real operational state.
|
|
51
|
+
|
|
52
|
+
## Runtime Model
|
|
53
|
+
|
|
54
|
+
PatchRelay is designed for a local, operator-owned setup:
|
|
55
|
+
|
|
56
|
+
- PatchRelay service runs on your machine or server
|
|
57
|
+
- Codex runs through `codex app-server`
|
|
58
|
+
- Linear is the control surface
|
|
59
|
+
- `patchrelay` CLI is the operator interface
|
|
60
|
+
- a reverse proxy exposes the Linear-facing routes
|
|
61
|
+
|
|
62
|
+
Linux and Node.js `24+` are the intended runtime.
|
|
63
|
+
|
|
64
|
+
You will also need:
|
|
65
|
+
|
|
66
|
+
- `git`
|
|
67
|
+
- `codex`
|
|
68
|
+
- a Linear OAuth app for this PatchRelay deployment
|
|
69
|
+
- a Linear webhook secret
|
|
70
|
+
- a public HTTPS entrypoint such as Caddy, nginx, or a tunnel so Linear can reach your PatchRelay webhook
|
|
71
|
+
|
|
72
|
+
For the exact OAuth app settings and webhook categories, use the Linear onboarding guide.
|
|
73
|
+
|
|
74
|
+
## How It Works
|
|
75
|
+
|
|
76
|
+
1. A human delegates PatchRelay on an issue to start automation, or mentions it to start a conversational agent session.
|
|
77
|
+
2. PatchRelay verifies the webhook and routes the issue to the right local project.
|
|
78
|
+
3. Delegated issues create or reuse the issue worktree and launch the matching workflow through `codex app-server`.
|
|
79
|
+
4. PatchRelay persists thread ids, run state, and observations so the work stays inspectable and resumable.
|
|
80
|
+
5. Mentions stay conversational, while delegated sessions and issue comments can steer the active run. An operator can take over from the exact same worktree when needed.
|
|
81
|
+
|
|
82
|
+
## Restart And Reconciliation
|
|
83
|
+
|
|
84
|
+
PatchRelay treats restart safety as part of the harness contract, not as a best-effort extra.
|
|
85
|
+
|
|
86
|
+
After a restart, the service should be able to answer:
|
|
87
|
+
|
|
88
|
+
- which issue owns each active worktree
|
|
89
|
+
- which stage was running or queued
|
|
90
|
+
- which Codex thread and turn belong to that work
|
|
91
|
+
- whether the issue is still eligible to continue
|
|
92
|
+
- whether the run should resume, hand off, or fail back to a human state
|
|
93
|
+
|
|
94
|
+
This is why PatchRelay keeps a small harness ledger alongside Codex thread history and Linear state. The goal is not to duplicate the model transcript. The goal is to make automation restartable, inspectable, and recoverable when the process or machine is interrupted.
|
|
95
|
+
|
|
96
|
+
## Workflow Configuration
|
|
97
|
+
|
|
98
|
+
PatchRelay keeps workflow configuration simple:
|
|
99
|
+
|
|
100
|
+
- route issues to a project by team, issue prefix, or labels
|
|
101
|
+
- when an issue is delegated to PatchRelay, it looks at the current Linear state
|
|
102
|
+
- that Linear state selects the matching workflow to run
|
|
103
|
+
- that workflow selects the repo-local workflow file
|
|
104
|
+
|
|
105
|
+
Most teams only configure:
|
|
106
|
+
|
|
107
|
+
- which issues belong to which project
|
|
108
|
+
- which Linear states should wake each workflow
|
|
109
|
+
- which workflow file belongs to each workflow
|
|
110
|
+
- which active state PatchRelay should set while that workflow is running
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
|
|
114
|
+
- a standard project can map `Start -> development`, `Review -> review`, and `Deploy -> deploy`
|
|
115
|
+
- a push-to-main project can automate implementation and review, then let GitHub Actions handle deployment while PatchRelay moves failures back to `Human Needed`
|
|
116
|
+
- a project with a QA gate can add a `qa` workflow bound to `Ready for QA`
|
|
117
|
+
|
|
118
|
+
## Access Control
|
|
119
|
+
|
|
120
|
+
PatchRelay reacts only for issues that route to a configured project.
|
|
121
|
+
|
|
122
|
+
- use `linear_team_ids`, `issue_key_prefixes`, and optional labels to keep unrelated or public boards out of scope
|
|
123
|
+
- in the normal setup, anyone with access to the routed Linear project can delegate work to the PatchRelay app
|
|
124
|
+
- use `trusted_actors` only when a project needs a narrower allowlist inside Linear
|
|
125
|
+
|
|
126
|
+
That keeps the default model simple without forcing an extra allowlist for every team.
|
|
127
|
+
|
|
128
|
+
## Quick Start
|
|
129
|
+
|
|
130
|
+
### 1. Install
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm install -g patchrelay
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 2. Bootstrap config
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
patchrelay init https://patchrelay.example.com
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`patchrelay init` requires the public HTTPS origin up front because Linear needs a fixed webhook URL and OAuth callback URL for this PatchRelay instance.
|
|
143
|
+
|
|
144
|
+
It creates the local config, env file, and user service units:
|
|
145
|
+
|
|
146
|
+
- `~/.config/patchrelay/runtime.env`
|
|
147
|
+
- `~/.config/patchrelay/service.env`
|
|
148
|
+
- `~/.config/patchrelay/patchrelay.json`
|
|
149
|
+
- `~/.config/systemd/user/patchrelay.service`
|
|
150
|
+
- `~/.config/systemd/user/patchrelay-reload.service`
|
|
151
|
+
- `~/.config/systemd/user/patchrelay.path`
|
|
152
|
+
|
|
153
|
+
The generated `patchrelay.json` is intentionally minimal, and `patchrelay init` prints the webhook URL, OAuth callback URL, and the Linear app values you need next.
|
|
154
|
+
|
|
155
|
+
### 3. Configure access
|
|
156
|
+
|
|
157
|
+
Edit `~/.config/patchrelay/service.env` and fill in only the Linear OAuth client values. Keep the generated webhook secret and token-encryption key:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
LINEAR_WEBHOOK_SECRET=generated-by-patchrelay-init
|
|
161
|
+
PATCHRELAY_TOKEN_ENCRYPTION_KEY=generated-by-patchrelay-init
|
|
162
|
+
LINEAR_OAUTH_CLIENT_ID=replace-with-linear-oauth-client-id
|
|
163
|
+
LINEAR_OAUTH_CLIENT_SECRET=replace-with-linear-oauth-client-secret
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Keep service secrets in `service.env`. `runtime.env` is for non-secret overrides such as `PATCHRELAY_DB_PATH` or `PATCHRELAY_LOG_FILE`. Everyday local inspection commands do not require exporting these values in your shell.
|
|
167
|
+
|
|
168
|
+
### 4. Configure a project
|
|
169
|
+
|
|
170
|
+
Add repositories after `patchrelay init` with `patchrelay project apply <id> <repo-path>`.
|
|
171
|
+
|
|
172
|
+
For a single project, that is usually enough. For multiple projects, add routing with `--issue-prefix APP` or `--team-id <linear-team-id>`.
|
|
173
|
+
|
|
174
|
+
The generated `~/.config/patchrelay/patchrelay.json` is machine-level service config only. Project entries should be created with the CLI, not by hand-editing a placeholder template.
|
|
175
|
+
|
|
176
|
+
`patchrelay project apply` is idempotent:
|
|
177
|
+
|
|
178
|
+
- it creates or updates the local project entry
|
|
179
|
+
- it checks whether PatchRelay is ready
|
|
180
|
+
- it reloads the service when it can
|
|
181
|
+
- it reuses or starts the Linear connect flow when the local setup is ready
|
|
182
|
+
- if workflow files or secrets are still missing, it tells you exactly what to fix and can be rerun safely
|
|
183
|
+
|
|
184
|
+
### 5. Add workflow docs to the repo
|
|
185
|
+
|
|
186
|
+
By default PatchRelay looks for:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
IMPLEMENTATION_WORKFLOW.md
|
|
190
|
+
REVIEW_WORKFLOW.md
|
|
191
|
+
DEPLOY_WORKFLOW.md
|
|
192
|
+
CLEANUP_WORKFLOW.md
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
These files define how the agent should work in that repo.
|
|
196
|
+
|
|
197
|
+
### 6. Validate
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
patchrelay doctor
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 7. Check the installation
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
patchrelay installations
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
In the normal happy path, the earlier `patchrelay project apply <id> <repo-path>` command already handles the connect step for you. `patchrelay connect --project <id>` still exists as the advanced/manual command when you want to retry or debug only the Linear authorization layer.
|
|
210
|
+
|
|
211
|
+
If you later add another local repo that should use the same Linear installation, run `patchrelay project apply <id> <repo-path>` for that repo too. PatchRelay now reuses the single saved installation automatically when there is no ambiguity, so you usually will not need another browser approval.
|
|
212
|
+
|
|
213
|
+
Important:
|
|
214
|
+
|
|
215
|
+
- Linear needs a public HTTPS URL to reach your webhook.
|
|
216
|
+
- `patchrelay init <public-base-url>` writes `server.public_base_url`, which PatchRelay uses when it prints webhook URLs.
|
|
217
|
+
- For ingress, OAuth app setup, and webhook details, use the self-hosting and Linear onboarding docs.
|
|
218
|
+
|
|
219
|
+
## Daily Loop
|
|
220
|
+
|
|
221
|
+
1. Delegate a Linear issue to the PatchRelay app.
|
|
222
|
+
2. PatchRelay reads the current Linear state like `Start`, `Ready for QA`, or `Deploy` to choose the matching workflow.
|
|
223
|
+
3. Linear sends the delegation and agent-session webhooks to PatchRelay, which creates or reuses the issue worktree and launches the matching workflow.
|
|
224
|
+
4. Follow up in the issue comments to steer the active run or wake it with fresh input while it remains delegated.
|
|
225
|
+
5. Watch progress from the terminal or open the same worktree and take over manually.
|
|
226
|
+
|
|
227
|
+
Useful commands:
|
|
228
|
+
|
|
229
|
+
- `patchrelay list --active`
|
|
230
|
+
- `patchrelay inspect APP-123`
|
|
231
|
+
- `patchrelay live APP-123 --watch`
|
|
232
|
+
- `patchrelay report APP-123`
|
|
233
|
+
- `patchrelay events APP-123 --follow`
|
|
234
|
+
- `patchrelay worktree APP-123 --cd`
|
|
235
|
+
- `patchrelay open APP-123`
|
|
236
|
+
- `patchrelay retry APP-123 --stage review`
|
|
237
|
+
|
|
238
|
+
`patchrelay open` is the handoff bridge: it opens Codex in the issue worktree and resumes the existing thread when PatchRelay has one.
|
|
239
|
+
|
|
240
|
+
Today that takeover path is intentionally YOLO mode: it launches Codex with `--dangerously-bypass-approvals-and-sandbox`.
|
|
241
|
+
|
|
242
|
+
## Operator View
|
|
243
|
+
|
|
244
|
+
PatchRelay keeps enough durable state to answer the questions that matter during and after a run:
|
|
245
|
+
|
|
246
|
+
- which worktree and branch belong to an issue
|
|
247
|
+
- which stage is active or queued
|
|
248
|
+
- which Codex thread owns the current work
|
|
249
|
+
- what the agent said
|
|
250
|
+
- which commands it ran
|
|
251
|
+
- which files it changed
|
|
252
|
+
- whether the stage completed, failed, or needs handoff
|
|
253
|
+
|
|
254
|
+
## Docs
|
|
255
|
+
|
|
256
|
+
Use the README for the product overview and quick start. Use the docs for operating details:
|
|
257
|
+
|
|
258
|
+
- [Self-hosting and deployment](https://github.com/krasnoperov/patchrelay/blob/main/docs/self-hosting.md)
|
|
259
|
+
- [Linear agent onboarding](https://github.com/krasnoperov/patchrelay/blob/main/docs/linear-agent-onboarding.md)
|
|
260
|
+
- [CLI reference](https://github.com/krasnoperov/patchrelay/blob/main/docs/cli-reference.md)
|
|
261
|
+
- [Architecture](https://github.com/krasnoperov/patchrelay/blob/main/docs/architecture.md)
|
|
262
|
+
- [Module map](https://github.com/krasnoperov/patchrelay/blob/main/docs/module-map.md)
|
|
263
|
+
- [Authoritative vs derived state](https://github.com/krasnoperov/patchrelay/blob/main/docs/state-authority.md)
|
|
264
|
+
- [Persistence audit](https://github.com/krasnoperov/patchrelay/blob/main/docs/persistence-audit.md)
|
|
265
|
+
- [Codex integration details](https://github.com/krasnoperov/patchrelay/blob/main/docs/codex-workflow.md)
|
|
266
|
+
- [Workflow file requirements](https://github.com/krasnoperov/patchrelay/blob/main/docs/IMPLEMENTATION_WORKFLOW_REQUIREMENTS.md)
|
|
267
|
+
- [Security policy](https://github.com/krasnoperov/patchrelay/blob/main/SECURITY.md)
|
|
268
|
+
|
|
269
|
+
## Status
|
|
270
|
+
|
|
271
|
+
PatchRelay is usable now, but still early and opinionated. The focus is a strong self-hosted harness for Linear + Codex work, not a generalized SaaS control plane.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getBundledAssetPath } from "./runtime-paths.js";
|
|
4
|
+
const fallbackBuildInfo = {
|
|
5
|
+
service: "patchrelay",
|
|
6
|
+
version: "0.1.0",
|
|
7
|
+
commit: "unknown",
|
|
8
|
+
builtAt: "unknown",
|
|
9
|
+
};
|
|
10
|
+
export function getBuildInfo() {
|
|
11
|
+
const buildInfoPath = path.resolve(process.cwd(), "dist/build-info.json");
|
|
12
|
+
const fallbackPath = getBundledAssetPath("dist/build-info.json");
|
|
13
|
+
const resolvedPath = existsSync(buildInfoPath) ? buildInfoPath : fallbackPath;
|
|
14
|
+
if (!existsSync(resolvedPath)) {
|
|
15
|
+
return fallbackBuildInfo;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(readFileSync(resolvedPath, "utf8"));
|
|
19
|
+
return {
|
|
20
|
+
service: typeof parsed.service === "string" ? parsed.service : fallbackBuildInfo.service,
|
|
21
|
+
version: typeof parsed.version === "string" ? parsed.version : fallbackBuildInfo.version,
|
|
22
|
+
commit: typeof parsed.commit === "string" ? parsed.commit : fallbackBuildInfo.commit,
|
|
23
|
+
builtAt: typeof parsed.builtAt === "string" ? parsed.builtAt : fallbackBuildInfo.builtAt,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return fallbackBuildInfo;
|
|
28
|
+
}
|
|
29
|
+
}
|