@owlmeans/viable-sdk 0.1.18-rc.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/README.md +128 -0
- package/build/api/index.d.ts +2 -0
- package/build/api/index.d.ts.map +1 -0
- package/build/api/index.js +2 -0
- package/build/api/index.js.map +1 -0
- package/build/api/remote.d.ts +14 -0
- package/build/api/remote.d.ts.map +1 -0
- package/build/api/remote.js +123 -0
- package/build/api/remote.js.map +1 -0
- package/build/consts.d.ts +47 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +47 -0
- package/build/consts.js.map +1 -0
- package/build/context/index.d.ts +22 -0
- package/build/context/index.d.ts.map +1 -0
- package/build/context/index.js +92 -0
- package/build/context/index.js.map +1 -0
- package/build/errors.d.ts +21 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +36 -0
- package/build/errors.js.map +1 -0
- package/build/executor/boot-check.d.ts +73 -0
- package/build/executor/boot-check.d.ts.map +1 -0
- package/build/executor/boot-check.js +155 -0
- package/build/executor/boot-check.js.map +1 -0
- package/build/executor/env.d.ts +25 -0
- package/build/executor/env.d.ts.map +1 -0
- package/build/executor/env.js +77 -0
- package/build/executor/env.js.map +1 -0
- package/build/executor/errors.d.ts +69 -0
- package/build/executor/errors.d.ts.map +1 -0
- package/build/executor/errors.js +80 -0
- package/build/executor/errors.js.map +1 -0
- package/build/executor/files.d.ts +124 -0
- package/build/executor/files.d.ts.map +1 -0
- package/build/executor/files.js +529 -0
- package/build/executor/files.js.map +1 -0
- package/build/executor/git.d.ts +52 -0
- package/build/executor/git.d.ts.map +1 -0
- package/build/executor/git.js +353 -0
- package/build/executor/git.js.map +1 -0
- package/build/executor/health.d.ts +92 -0
- package/build/executor/health.d.ts.map +1 -0
- package/build/executor/health.js +98 -0
- package/build/executor/health.js.map +1 -0
- package/build/executor/index.d.ts +39 -0
- package/build/executor/index.d.ts.map +1 -0
- package/build/executor/index.js +173 -0
- package/build/executor/index.js.map +1 -0
- package/build/executor/integrity.d.ts +17 -0
- package/build/executor/integrity.d.ts.map +1 -0
- package/build/executor/integrity.js +59 -0
- package/build/executor/integrity.js.map +1 -0
- package/build/executor/layout.d.ts +61 -0
- package/build/executor/layout.d.ts.map +1 -0
- package/build/executor/layout.js +98 -0
- package/build/executor/layout.js.map +1 -0
- package/build/executor/shell.d.ts +89 -0
- package/build/executor/shell.d.ts.map +1 -0
- package/build/executor/shell.js +280 -0
- package/build/executor/shell.js.map +1 -0
- package/build/executor/spawn.d.ts +111 -0
- package/build/executor/spawn.d.ts.map +1 -0
- package/build/executor/spawn.js +314 -0
- package/build/executor/spawn.js.map +1 -0
- package/build/harness/index.d.ts +28 -0
- package/build/harness/index.d.ts.map +1 -0
- package/build/harness/index.js +83 -0
- package/build/harness/index.js.map +1 -0
- package/build/harness/templates.d.ts +30 -0
- package/build/harness/templates.d.ts.map +1 -0
- package/build/harness/templates.js +162 -0
- package/build/harness/templates.js.map +1 -0
- package/build/index.d.ts +13 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +12 -0
- package/build/index.js.map +1 -0
- package/build/project/env.d.ts +108 -0
- package/build/project/env.d.ts.map +1 -0
- package/build/project/env.js +233 -0
- package/build/project/env.js.map +1 -0
- package/build/project/marker.d.ts +34 -0
- package/build/project/marker.d.ts.map +1 -0
- package/build/project/marker.js +54 -0
- package/build/project/marker.js.map +1 -0
- package/build/project/setup.d.ts +62 -0
- package/build/project/setup.d.ts.map +1 -0
- package/build/project/setup.js +190 -0
- package/build/project/setup.js.map +1 -0
- package/build/run/index.d.ts +75 -0
- package/build/run/index.d.ts.map +1 -0
- package/build/run/index.js +150 -0
- package/build/run/index.js.map +1 -0
- package/build/run/serve.d.ts +18 -0
- package/build/run/serve.d.ts.map +1 -0
- package/build/run/serve.js +148 -0
- package/build/run/serve.js.map +1 -0
- package/build/run/state.d.ts +75 -0
- package/build/run/state.d.ts.map +1 -0
- package/build/run/state.js +136 -0
- package/build/run/state.js.map +1 -0
- package/build/session/index.d.ts +17 -0
- package/build/session/index.d.ts.map +1 -0
- package/build/session/index.js +226 -0
- package/build/session/index.js.map +1 -0
- package/build/session/questions.d.ts +16 -0
- package/build/session/questions.d.ts.map +1 -0
- package/build/session/questions.js +17 -0
- package/build/session/questions.js.map +1 -0
- package/build/session/queue.d.ts +54 -0
- package/build/session/queue.d.ts.map +1 -0
- package/build/session/queue.js +102 -0
- package/build/session/queue.js.map +1 -0
- package/build/session/tasks.d.ts +15 -0
- package/build/session/tasks.d.ts.map +1 -0
- package/build/session/tasks.js +16 -0
- package/build/session/tasks.js.map +1 -0
- package/build/task/driver.d.ts +30 -0
- package/build/task/driver.d.ts.map +1 -0
- package/build/task/driver.js +120 -0
- package/build/task/driver.js.map +1 -0
- package/build/task/envelope.d.ts +35 -0
- package/build/task/envelope.d.ts.map +1 -0
- package/build/task/envelope.js +230 -0
- package/build/task/envelope.js.map +1 -0
- package/build/task/index.d.ts +4 -0
- package/build/task/index.d.ts.map +1 -0
- package/build/task/index.js +4 -0
- package/build/task/index.js.map +1 -0
- package/build/task/inquiry.d.ts +53 -0
- package/build/task/inquiry.d.ts.map +1 -0
- package/build/task/inquiry.js +232 -0
- package/build/task/inquiry.js.map +1 -0
- package/build/tools/catalogue.d.ts +17 -0
- package/build/tools/catalogue.d.ts.map +1 -0
- package/build/tools/catalogue.js +1169 -0
- package/build/tools/catalogue.js.map +1 -0
- package/build/tools/index.d.ts +7 -0
- package/build/tools/index.d.ts.map +1 -0
- package/build/tools/index.js +7 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/jobs.d.ts +12 -0
- package/build/tools/jobs.d.ts.map +1 -0
- package/build/tools/jobs.js +60 -0
- package/build/tools/jobs.js.map +1 -0
- package/build/tools/mcp.d.ts +43 -0
- package/build/tools/mcp.d.ts.map +1 -0
- package/build/tools/mcp.js +131 -0
- package/build/tools/mcp.js.map +1 -0
- package/build/tools/platform.d.ts +77 -0
- package/build/tools/platform.d.ts.map +1 -0
- package/build/tools/platform.js +316 -0
- package/build/tools/platform.js.map +1 -0
- package/build/tools/refusal.d.ts +66 -0
- package/build/tools/refusal.d.ts.map +1 -0
- package/build/tools/refusal.js +377 -0
- package/build/tools/refusal.js.map +1 -0
- package/build/tools/types.d.ts +83 -0
- package/build/tools/types.d.ts.map +1 -0
- package/build/tools/types.js +38 -0
- package/build/tools/types.js.map +1 -0
- package/build/types.d.ts +174 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import p from 'node:path';
|
|
4
|
+
import { SlotGitCommand } from '@owlmeans/viable-common';
|
|
5
|
+
import { GitDirtyTree, LocalGitError } from './errors.js';
|
|
6
|
+
import { MAX_OUTPUT_BUFFER } from './spawn.js';
|
|
7
|
+
/**
|
|
8
|
+
* The git half of the local executor, over the `git` CLI.
|
|
9
|
+
*
|
|
10
|
+
* The platform's own implementation is `@owlmeans/git`, which is private and carries `simple-git`
|
|
11
|
+
* and a GitHub client — neither of which may reach a package a developer installs to drive their
|
|
12
|
+
* own machine. So the semantics are ported and the two constants below are copied, with this
|
|
13
|
+
* comment as the record of where they came from: a connector that invented its own author name
|
|
14
|
+
* would produce a history that does not match what the platform writes for the same project in a
|
|
15
|
+
* slot.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_BRANCH = 'main';
|
|
18
|
+
const DEFAULT_GIT_USER_NAME = 'OwlMeans Viable';
|
|
19
|
+
const DEFAULT_GIT_USER_EMAIL = 'agent@owlmeans.com';
|
|
20
|
+
const LOG_LIMIT = 20;
|
|
21
|
+
const STATUS_FILES_CAP = 50;
|
|
22
|
+
/**
|
|
23
|
+
* The runtime safety net `ensure` appends, so a first commit never captures dependencies, build
|
|
24
|
+
* output, secrets or the connector's own scratch files.
|
|
25
|
+
*
|
|
26
|
+
* `.viable/connect.json` is deliberately NOT here: it is the project's identity, it holds no
|
|
27
|
+
* secret, and a developer who clones their own repository elsewhere should find it. The two files
|
|
28
|
+
* beside it are — a run record names pids on one machine, and the local keypair is a credential.
|
|
29
|
+
*/
|
|
30
|
+
const IGNORE_BASELINE = [
|
|
31
|
+
'node_modules/',
|
|
32
|
+
'dist/',
|
|
33
|
+
'build/',
|
|
34
|
+
'.env',
|
|
35
|
+
'.viable/run.json',
|
|
36
|
+
'.viable/local.json',
|
|
37
|
+
'tsconfig.tsbuildinfo',
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Why a local repository is never pushed, pulled or given a remote.
|
|
41
|
+
*
|
|
42
|
+
* DELIBERATE DIFFERENCE from the publisher, which does all three against a token the platform
|
|
43
|
+
* holds. A slot's volume is the platform's and its remote is a repository the platform created;
|
|
44
|
+
* a developer's checkout is THEIRS, its remote is whatever they configured, and pushing to it
|
|
45
|
+
* would mean the platform writing to a repository nobody here was asked about — with the
|
|
46
|
+
* developer's own credential helper supplying the authentication. So the platform touches none
|
|
47
|
+
* of it: whoever owns the directory owns its remotes.
|
|
48
|
+
*/
|
|
49
|
+
const REMOTE_REFUSAL = 'This project lives on your own machine, so the platform does not touch its '
|
|
50
|
+
+ 'git remotes. Push, pull and remote configuration are yours to run.';
|
|
51
|
+
/**
|
|
52
|
+
* Why a local target is never cloned into.
|
|
53
|
+
*
|
|
54
|
+
* A clone exists for one case — the platform fetching an origin repository onto a slot's volume it
|
|
55
|
+
* owns — and a local target has already answered that question: the directory the connector was
|
|
56
|
+
* started in IS the origin, and there is nothing to fetch. Overwriting it with a remote tree would
|
|
57
|
+
* replace a developer's working copy, including whatever they had not committed.
|
|
58
|
+
*
|
|
59
|
+
* Answered as TEXT beside {@link REMOTE_REFUSAL} and never thrown, for the same reason: a caller
|
|
60
|
+
* that received an exception would retry something that can never succeed, while a `cloned: false`
|
|
61
|
+
* in the shape it already parses is a fact its next step can read.
|
|
62
|
+
*/
|
|
63
|
+
const CLONE_REFUSAL = 'This project is the directory the connector was started in, so there is '
|
|
64
|
+
+ 'nothing to clone into it. Its sources are already here.';
|
|
65
|
+
/**
|
|
66
|
+
* Tail of the serialization chain per working directory.
|
|
67
|
+
*
|
|
68
|
+
* Git takes `.git/*.lock` files for the duration of a write, so two concurrent operations on one
|
|
69
|
+
* repository fail with `could not lock config file .git/config: File exists`. That is not
|
|
70
|
+
* hypothetical: every command here calls `ensure()` first, and two unsynchronized producers
|
|
71
|
+
* routinely overlap — an explicit status call and whatever status the connector rides along with
|
|
72
|
+
* its own reporting.
|
|
73
|
+
*/
|
|
74
|
+
const _chains = new Map();
|
|
75
|
+
const serialize = async (dir, op) => {
|
|
76
|
+
// Chain onto whatever is already queued for this dir, running `op` whether the previous command
|
|
77
|
+
// resolved or rejected so one failure never poisons the queue behind it.
|
|
78
|
+
const previous = _chains.get(dir) ?? Promise.resolve();
|
|
79
|
+
const result = previous.then(op, op);
|
|
80
|
+
const tail = result.then(() => undefined, () => undefined);
|
|
81
|
+
_chains.set(dir, tail);
|
|
82
|
+
// Drop the entry once nothing else has queued behind it, so the map cannot grow without bound.
|
|
83
|
+
void tail.then(() => {
|
|
84
|
+
if (_chains.get(dir) === tail) {
|
|
85
|
+
_chains.delete(dir);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return await result;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Run one git command.
|
|
92
|
+
*
|
|
93
|
+
* `core.quotePath=false` so a path with a non-ASCII character comes back as itself rather than as
|
|
94
|
+
* an escaped C string nothing downstream un-escapes. `GIT_TERMINAL_PROMPT=0` so a command that
|
|
95
|
+
* wants credentials fails instead of blocking on a prompt no one can answer.
|
|
96
|
+
*/
|
|
97
|
+
const git = async (dir, args) => await new Promise(resolve => {
|
|
98
|
+
execFile('git', ['-c', 'core.quotePath=false', ...args], {
|
|
99
|
+
cwd: dir,
|
|
100
|
+
maxBuffer: MAX_OUTPUT_BUFFER,
|
|
101
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
102
|
+
}, (error, stdout, stderr) => resolve({ ok: error == null, out: stdout, err: stderr }));
|
|
103
|
+
});
|
|
104
|
+
// Unit separator: it cannot appear in any of the fields, so a subject with tabs or spaces in it
|
|
105
|
+
// still parses.
|
|
106
|
+
const LOG_FORMAT = '--format=%H%x1f%h%x1f%an%x1f%ae%x1f%cI%x1f%s';
|
|
107
|
+
const parseCommits = (out) => out
|
|
108
|
+
.split('\n')
|
|
109
|
+
.map(line => line.trim())
|
|
110
|
+
.filter(line => line !== '')
|
|
111
|
+
.map(line => {
|
|
112
|
+
const [hash, shortHash, authorName, authorEmail, committedAt, ...subject] = line.split('\x1f');
|
|
113
|
+
return {
|
|
114
|
+
hash, shortHash, authorName, authorEmail, committedAt, subject: subject.join('\x1f'),
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
const headInfo = async (dir) => {
|
|
118
|
+
const result = await git(dir, ['log', '-1', LOG_FORMAT]);
|
|
119
|
+
return result.ok ? parseCommits(result.out)[0] ?? null : null;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Whether the tree has anything uncommitted, untracked files included.
|
|
123
|
+
*
|
|
124
|
+
* Asked before `add -A` rather than after, so a clean tree is reported as clean instead of
|
|
125
|
+
* producing an empty commit.
|
|
126
|
+
*/
|
|
127
|
+
const isDirty = async (dir) => {
|
|
128
|
+
const result = await git(dir, ['status', '--porcelain', '-u']);
|
|
129
|
+
return result.out.trim() !== '';
|
|
130
|
+
};
|
|
131
|
+
const parseStatus = (out) => {
|
|
132
|
+
const lines = out.split('\n').filter(line => line !== '');
|
|
133
|
+
let branch = DEFAULT_BRANCH;
|
|
134
|
+
let ahead = null;
|
|
135
|
+
let behind = null;
|
|
136
|
+
const files = [];
|
|
137
|
+
for (const line of lines) {
|
|
138
|
+
if (line.startsWith('## ')) {
|
|
139
|
+
let rest = line.slice(3);
|
|
140
|
+
const tracking = /\s\[(.+)\]$/.exec(rest);
|
|
141
|
+
if (tracking != null) {
|
|
142
|
+
rest = rest.slice(0, tracking.index);
|
|
143
|
+
const aheadMatch = /ahead (\d+)/.exec(tracking[1]);
|
|
144
|
+
const behindMatch = /behind (\d+)/.exec(tracking[1]);
|
|
145
|
+
if (aheadMatch != null)
|
|
146
|
+
ahead = parseInt(aheadMatch[1]);
|
|
147
|
+
if (behindMatch != null)
|
|
148
|
+
behind = parseInt(behindMatch[1]);
|
|
149
|
+
// A branch with an upstream but no divergence reports neither word; both are zero.
|
|
150
|
+
if (aheadMatch == null && behindMatch == null && !/gone/.test(tracking[1])) {
|
|
151
|
+
ahead = 0;
|
|
152
|
+
behind = 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// `## No commits yet on main` is what an unborn branch says about itself.
|
|
156
|
+
branch = rest.split('...')[0].replace(/^No commits yet on /, '').trim();
|
|
157
|
+
if (branch === '' || branch === 'HEAD (no branch)')
|
|
158
|
+
branch = DEFAULT_BRANCH;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const status = `${line[0] ?? ''}${line[1] ?? ''}`.trim();
|
|
162
|
+
const raw = line.slice(3);
|
|
163
|
+
// A rename reads `R old -> new`; the new path is the one that exists.
|
|
164
|
+
const path = raw.includes(' -> ') ? raw.slice(raw.indexOf(' -> ') + 4) : raw;
|
|
165
|
+
files.push({ path, status });
|
|
166
|
+
}
|
|
167
|
+
return { branch, files, ahead, behind };
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Ahead/behind against the remote-tracking ref, when porcelain did not say.
|
|
171
|
+
*
|
|
172
|
+
* A branch with no upstream configured still has an `origin/<branch>` ref after a fetch, and that
|
|
173
|
+
* is what a caller wants to know about.
|
|
174
|
+
*/
|
|
175
|
+
const localCounts = async (dir, branch) => {
|
|
176
|
+
const result = await git(dir, [
|
|
177
|
+
'rev-list', '--left-right', '--count', `${branch}...origin/${branch}`
|
|
178
|
+
]);
|
|
179
|
+
if (!result.ok) {
|
|
180
|
+
return { ahead: null, behind: null };
|
|
181
|
+
}
|
|
182
|
+
const parts = result.out.trim().split(/\s+/);
|
|
183
|
+
return parts.length === 2
|
|
184
|
+
? { ahead: parseInt(parts[0]), behind: parseInt(parts[1]) }
|
|
185
|
+
: { ahead: null, behind: null };
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Make the directory a repository the platform can work in, and change nothing else.
|
|
189
|
+
*
|
|
190
|
+
* DELIBERATE DIFFERENCE from the publisher on two points, both because this history belongs to a
|
|
191
|
+
* person rather than to a slot:
|
|
192
|
+
*
|
|
193
|
+
* - **No baseline commit.** The publisher snapshots a pre-git slot into a first commit so its
|
|
194
|
+
* history starts clean. Here the first commit is the developer's to make (or not) — a tool that
|
|
195
|
+
* committed a working tree it found is a tool that decided what their project's history says.
|
|
196
|
+
* - **An identity only when there is none.** `git config user.name` consults local, global and
|
|
197
|
+
* system, so a developer with a global identity keeps authoring as themselves. Writing the
|
|
198
|
+
* platform's name unconditionally would rewrite the author of every commit made in that
|
|
199
|
+
* directory afterwards, including ones the platform had nothing to do with.
|
|
200
|
+
*/
|
|
201
|
+
const ensure = async (dir) => {
|
|
202
|
+
const created = await fsp.stat(p.join(dir, '.git')).then(() => false, () => true);
|
|
203
|
+
if (created) {
|
|
204
|
+
const init = await git(dir, ['init', '-b', DEFAULT_BRANCH]);
|
|
205
|
+
if (!init.ok) {
|
|
206
|
+
// `-b` arrived in git 2.28; an older one still initializes and is then pointed at `main`.
|
|
207
|
+
const fallback = await git(dir, ['init']);
|
|
208
|
+
if (!fallback.ok) {
|
|
209
|
+
throw new LocalGitError(`git init failed in ${dir}: ${fallback.err.trim()}`);
|
|
210
|
+
}
|
|
211
|
+
await git(dir, ['symbolic-ref', 'HEAD', `refs/heads/${DEFAULT_BRANCH}`]);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const [key, value] of [
|
|
215
|
+
['user.name', DEFAULT_GIT_USER_NAME], ['user.email', DEFAULT_GIT_USER_EMAIL]
|
|
216
|
+
]) {
|
|
217
|
+
const current = await git(dir, ['config', '--get', key]);
|
|
218
|
+
if (!current.ok || current.out.trim() === '') {
|
|
219
|
+
await git(dir, ['config', '--local', key, value]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const ignorePath = p.join(dir, '.gitignore');
|
|
223
|
+
const existing = await fsp.readFile(ignorePath, 'utf8').catch(() => '');
|
|
224
|
+
const lines = new Set(existing.split('\n').map(line => line.trim()));
|
|
225
|
+
const missing = IGNORE_BASELINE.filter(entry => !lines.has(entry));
|
|
226
|
+
if (missing.length > 0) {
|
|
227
|
+
const prefix = existing === '' || existing.endsWith('\n') ? existing : `${existing}\n`;
|
|
228
|
+
await fsp.writeFile(ignorePath, `${prefix}${missing.join('\n')}\n`);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const status = async (dir) => {
|
|
232
|
+
const porcelain = await git(dir, ['status', '--porcelain=v1', '-b', '-u']);
|
|
233
|
+
const { branch, files, ahead, behind } = parseStatus(porcelain.out);
|
|
234
|
+
const remote = await git(dir, ['remote', 'get-url', 'origin']);
|
|
235
|
+
const remoteUrl = remote.ok && remote.out.trim() !== '' ? remote.out.trim() : null;
|
|
236
|
+
const counts = ahead != null || behind != null
|
|
237
|
+
? { ahead, behind }
|
|
238
|
+
: await localCounts(dir, branch);
|
|
239
|
+
return {
|
|
240
|
+
initialized: true,
|
|
241
|
+
head: await headInfo(dir),
|
|
242
|
+
branch,
|
|
243
|
+
dirty: files.length > 0,
|
|
244
|
+
changedFiles: files.length,
|
|
245
|
+
files: files.slice(0, STATUS_FILES_CAP),
|
|
246
|
+
remoteUrl,
|
|
247
|
+
ahead: counts.ahead,
|
|
248
|
+
behind: counts.behind,
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
export const dispatchGitCommand = async (dir, command, args) => await serialize(dir, async () => await _dispatch(dir, command, args));
|
|
252
|
+
const _dispatch = async (dir, command, args) => {
|
|
253
|
+
switch (command) {
|
|
254
|
+
case SlotGitCommand.Ensure:
|
|
255
|
+
await ensure(dir);
|
|
256
|
+
return {};
|
|
257
|
+
case SlotGitCommand.Status:
|
|
258
|
+
await ensure(dir);
|
|
259
|
+
return await status(dir);
|
|
260
|
+
case SlotGitCommand.Commit: {
|
|
261
|
+
await ensure(dir);
|
|
262
|
+
if (!await isDirty(dir)) {
|
|
263
|
+
return { commit: null };
|
|
264
|
+
}
|
|
265
|
+
const add = await git(dir, ['add', '-A']);
|
|
266
|
+
if (!add.ok) {
|
|
267
|
+
throw new LocalGitError(`git add failed: ${add.err.trim()}`);
|
|
268
|
+
}
|
|
269
|
+
const message = typeof args?.message === 'string' && args.message !== ''
|
|
270
|
+
? args.message
|
|
271
|
+
: 'chore: update project';
|
|
272
|
+
const commit = await git(dir, ['commit', '-m', message]);
|
|
273
|
+
if (!commit.ok) {
|
|
274
|
+
throw new LocalGitError(`git commit failed: ${(commit.err || commit.out).trim()}`);
|
|
275
|
+
}
|
|
276
|
+
return { commit: await headInfo(dir) };
|
|
277
|
+
}
|
|
278
|
+
case SlotGitCommand.Log: {
|
|
279
|
+
await ensure(dir);
|
|
280
|
+
const result = await git(dir, ['log', `--max-count=${LOG_LIMIT}`, LOG_FORMAT]);
|
|
281
|
+
return { commits: result.ok ? parseCommits(result.out) : [] };
|
|
282
|
+
}
|
|
283
|
+
case SlotGitCommand.Discard: {
|
|
284
|
+
await ensure(dir);
|
|
285
|
+
// On an unborn branch HEAD does not exist — there is nothing to reset to, and a `clean`
|
|
286
|
+
// there would delete a tree nobody has committed yet.
|
|
287
|
+
if (await headInfo(dir) == null) {
|
|
288
|
+
return await status(dir);
|
|
289
|
+
}
|
|
290
|
+
await git(dir, ['reset', '--hard', 'HEAD']);
|
|
291
|
+
// `.viable/` is excluded by name: the marker is what identifies this directory as a
|
|
292
|
+
// platform project at all, it is untracked until someone commits it, and a discard that
|
|
293
|
+
// swept it would leave a tree no connector can recognize afterwards.
|
|
294
|
+
await git(dir, ['clean', '-fd', '-e', '.viable']);
|
|
295
|
+
return await status(dir);
|
|
296
|
+
}
|
|
297
|
+
case SlotGitCommand.RevertTo: {
|
|
298
|
+
await ensure(dir);
|
|
299
|
+
const hash = typeof args?.hash === 'string' ? args.hash : '';
|
|
300
|
+
if (hash === '') {
|
|
301
|
+
throw new LocalGitError('revert-no-hash');
|
|
302
|
+
}
|
|
303
|
+
// A revert overwrites the working tree, so uncommitted work would be lost with no way back.
|
|
304
|
+
if (await isDirty(dir)) {
|
|
305
|
+
throw new GitDirtyTree(dir);
|
|
306
|
+
}
|
|
307
|
+
const target = await git(dir, ['log', '-1', '--format=%h%x1f%s', hash]);
|
|
308
|
+
if (!target.ok) {
|
|
309
|
+
throw new LocalGitError(`invalid-hash:${hash}`);
|
|
310
|
+
}
|
|
311
|
+
const [shortHash, ...subjectParts] = target.out.trim().split('\x1f');
|
|
312
|
+
const subject = subjectParts.join('\x1f');
|
|
313
|
+
// APPEND-ONLY. `read-tree` restores the old content as a NEW commit on top of the current
|
|
314
|
+
// history, so nothing that ever happened is unmade — a reset would rewrite history a person
|
|
315
|
+
// may already have pushed or be working from.
|
|
316
|
+
const read = await git(dir, ['read-tree', '-u', '--reset', hash]);
|
|
317
|
+
if (!read.ok) {
|
|
318
|
+
throw new LocalGitError(`revert-failed: ${read.err.trim()}`);
|
|
319
|
+
}
|
|
320
|
+
// Reverting to what HEAD already holds changes nothing; answering with the current head is
|
|
321
|
+
// the honest result, and an empty commit would only add noise to the history.
|
|
322
|
+
if (!await isDirty(dir)) {
|
|
323
|
+
return { commit: await headInfo(dir) };
|
|
324
|
+
}
|
|
325
|
+
const commit = await git(dir, ['commit', '-m', `revert: restore ${shortHash} — ${subject}`]);
|
|
326
|
+
if (!commit.ok) {
|
|
327
|
+
throw new LocalGitError(`revert-commit-failed: ${(commit.err || commit.out).trim()}`);
|
|
328
|
+
}
|
|
329
|
+
const head = await headInfo(dir);
|
|
330
|
+
if (head == null) {
|
|
331
|
+
throw new LocalGitError('revert-no-head');
|
|
332
|
+
}
|
|
333
|
+
return { commit: head };
|
|
334
|
+
}
|
|
335
|
+
// The three that touch a remote. Answered as TEXT rather than thrown: this is a policy, not a
|
|
336
|
+
// failure, and a caller that treats it as an error would retry something that can never
|
|
337
|
+
// succeed. The `GitSyncResult` fields travel beside it so a caller reading that shape finds
|
|
338
|
+
// the fields it expects instead of `undefined`.
|
|
339
|
+
case SlotGitCommand.SetRemote:
|
|
340
|
+
case SlotGitCommand.Push:
|
|
341
|
+
case SlotGitCommand.Pull:
|
|
342
|
+
return { result: REMOTE_REFUSAL, status: 'no-remote', ahead: null, behind: null, message: REMOTE_REFUSAL };
|
|
343
|
+
// The fourth, refused in the same shape and for the same reason — see CLONE_REFUSAL. The
|
|
344
|
+
// convert pipeline skips its clone step on a local target, so this is the belt to that
|
|
345
|
+
// braces: a command that arrives anyway answers a fact rather than failing a run.
|
|
346
|
+
case SlotGitCommand.Clone:
|
|
347
|
+
return { cloned: false, branch: '', head: null, result: CLONE_REFUSAL };
|
|
348
|
+
default:
|
|
349
|
+
throw new LocalGitError(`Unknown git command: ${String(command)}`);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
export { CLONE_REFUSAL, ensure as ensureGitRepo, IGNORE_BASELINE, REMOTE_REFUSAL };
|
|
353
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/executor/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,GAAG,MAAM,kBAAkB,CAAA;AAClC,OAAO,CAAC,MAAM,WAAW,CAAA;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAGxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;;;;GASG;AACH,MAAM,cAAc,GAAG,MAAM,CAAA;AAC7B,MAAM,qBAAqB,GAAG,iBAAiB,CAAA;AAC/C,MAAM,sBAAsB,GAAG,oBAAoB,CAAA;AAEnD,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAE3B;;;;;;;GAOG;AACH,MAAM,eAAe,GAAG;IACtB,eAAe;IACf,OAAO;IACP,QAAQ;IACR,MAAM;IACN,kBAAkB;IAClB,oBAAoB;IACpB,sBAAsB;CACvB,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,cAAc,GAAG,6EAA6E;MAChG,oEAAoE,CAAA;AAExE;;;;;;;;;;;GAWG;AACH,MAAM,aAAa,GAAG,0EAA0E;MAC5F,yDAAyD,CAAA;AAE7D;;;;;;;;GAQG;AACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAA4B,CAAA;AAEnD,MAAM,SAAS,GAAG,KAAK,EAAK,GAAW,EAAE,EAAoB,EAAc,EAAE;IAC3E,gGAAgG;IAChG,yEAAyE;IACzE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;IACtD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IAEpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAC1D,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACtB,+FAA+F;IAC/F,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;QAClB,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACrB,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,MAAM,CAAA;AACrB,CAAC,CAAA;AAQD;;;;;;GAMG;AACH,MAAM,GAAG,GAAG,KAAK,EAAE,GAAW,EAAE,IAAc,EAAsB,EAAE,CACpE,MAAM,IAAI,OAAO,CAAY,OAAO,CAAC,EAAE;IACrC,QAAQ,CACN,KAAK,EACL,CAAC,IAAI,EAAE,sBAAsB,EAAE,GAAG,IAAI,CAAC,EACvC;QACE,GAAG,EAAE,GAAG;QACR,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE,GAAG,EAAE;KAClD,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,IAAI,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CACpF,CAAA;AACH,CAAC,CAAC,CAAA;AAEJ,gGAAgG;AAChG,gBAAgB;AAChB,MAAM,UAAU,GAAG,8CAA8C,CAAA;AAEjE,MAAM,YAAY,GAAG,CAAC,GAAW,EAAuB,EAAE,CAAC,GAAG;KAC3D,KAAK,CAAC,IAAI,CAAC;KACX,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KACxB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;KAC3B,GAAG,CAAC,IAAI,CAAC,EAAE;IACV,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAE9F,OAAO;QACL,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;KACzD,CAAA;AAC/B,CAAC,CAAC,CAAA;AAEJ,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAW,EAAqC,EAAE;IACxE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAA;IAExD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC/D,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,OAAO,GAAG,KAAK,EAAE,GAAW,EAAoB,EAAE;IACtD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,CAAA;IAE9D,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;AACjC,CAAC,CAAA;AAED,MAAM,WAAW,GAAG,CAAC,GAAW,EAE9B,EAAE;IACF,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAA;IACzD,IAAI,MAAM,GAAG,cAAc,CAAA;IAC3B,IAAI,KAAK,GAAkB,IAAI,CAAA;IAC/B,IAAI,MAAM,GAAkB,IAAI,CAAA;IAChC,MAAM,KAAK,GAAwB,EAAE,CAAA;IAErC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACxB,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACzC,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;gBACrB,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACpC,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;gBAClD,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;gBACpD,IAAI,UAAU,IAAI,IAAI;oBAAE,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;gBACvD,IAAI,WAAW,IAAI,IAAI;oBAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;gBAC1D,mFAAmF;gBACnF,IAAI,UAAU,IAAI,IAAI,IAAI,WAAW,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC3E,KAAK,GAAG,CAAC,CAAA;oBACT,MAAM,GAAG,CAAC,CAAA;gBACZ,CAAC;YACH,CAAC;YACD,0EAA0E;YAC1E,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YACvE,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK,kBAAkB;gBAAE,MAAM,GAAG,cAAc,CAAA;YAC3E,SAAQ;QACV,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAA;QACxD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACzB,uEAAuE;QACvE,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QAC5E,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AACzC,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,WAAW,GAAG,KAAK,EACvB,GAAW,EAAE,MAAc,EAC+B,EAAE;IAC5D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE;QAC5B,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,MAAM,aAAa,MAAM,EAAE;KACtE,CAAC,CAAA;IACF,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IACtC,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAE5C,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;QACvB,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;QAC3D,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;AACnC,CAAC,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,GAAG,KAAK,EAAE,GAAW,EAAiB,EAAE;IAClD,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IACjF,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAA;QAC3D,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,0FAA0F;YAC1F,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;YACzC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,aAAa,CAAC,sBAAsB,GAAG,KAAK,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;YAC9E,CAAC;YACD,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,cAAc,cAAc,EAAE,CAAC,CAAC,CAAA;QAC1E,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI;QACzB,CAAC,WAAW,EAAE,qBAAqB,CAAC,EAAE,CAAC,YAAY,EAAE,sBAAsB,CAAC;KACpE,EAAE,CAAC;QACX,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;QACxD,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC7C,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;QACnD,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;IAC5C,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACvE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IACpE,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;IAClE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,QAAQ,KAAK,EAAE,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,CAAA;QACtF,MAAM,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrE,CAAC;AACH,CAAC,CAAA;AAED,MAAM,MAAM,GAAG,KAAK,EAAE,GAAW,EAA0B,EAAE;IAC3D,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IAC1E,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IAEnE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;IAC9D,MAAM,SAAS,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAElF,MAAM,MAAM,GAAG,KAAK,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI;QAC5C,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE;QACnB,CAAC,CAAC,MAAM,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAElC,OAAO;QACL,WAAW,EAAE,IAAI;QACjB,IAAI,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC;QACzB,MAAM;QACN,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC;QACvB,YAAY,EAAE,KAAK,CAAC,MAAM;QAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC;QACvC,SAAS;QACT,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,GAAW,EAAE,OAAuB,EAAE,IAA0B,EAC9B,EAAE,CACpC,MAAM,SAAS,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAA;AAEvE,MAAM,SAAS,GAAG,KAAK,EACrB,GAAW,EAAE,OAAuB,EAAE,IAA0B,EAC9B,EAAE;IACpC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,cAAc,CAAC,MAAM;YACxB,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;YAEjB,OAAO,EAAE,CAAA;QAEX,KAAK,cAAc,CAAC,MAAM;YACxB,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;YAEjB,OAAO,MAAM,MAAM,CAAC,GAAG,CAAuC,CAAA;QAEhE,KAAK,cAAc,CAAC,MAAM,EAAE,CAAC;YAC3B,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;YACjB,IAAI,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;YACzB,CAAC;YAED,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;YACzC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,aAAa,CAAC,mBAAmB,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;YAC9D,CAAC;YACD,MAAM,OAAO,GAAG,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,KAAK,EAAE;gBACtE,CAAC,CAAC,IAAI,CAAC,OAAO;gBACd,CAAC,CAAC,uBAAuB,CAAA;YAC3B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;YACxD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,IAAI,aAAa,CAAC,sBAAsB,CAAC,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;YACpF,CAAC;YAED,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAA;QACxC,CAAC;QAED,KAAK,cAAc,CAAC,GAAG,EAAE,CAAC;YACxB,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;YACjB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,eAAe,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC,CAAA;YAE9E,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;QAC/D,CAAC;QAED,KAAK,cAAc,CAAC,OAAO,EAAE,CAAC;YAC5B,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;YACjB,wFAAwF;YACxF,sDAAsD;YACtD,IAAI,MAAM,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;gBAChC,OAAO,MAAM,MAAM,CAAC,GAAG,CAAuC,CAAA;YAChE,CAAC;YAED,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAA;YAC3C,oFAAoF;YACpF,wFAAwF;YACxF,qEAAqE;YACrE,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAA;YAEjD,OAAO,MAAM,MAAM,CAAC,GAAG,CAAuC,CAAA;QAChE,CAAC;QAED,KAAK,cAAc,CAAC,QAAQ,EAAE,CAAC;YAC7B,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;YACjB,MAAM,IAAI,GAAG,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;YAC5D,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;gBAChB,MAAM,IAAI,aAAa,CAAC,gBAAgB,CAAC,CAAA;YAC3C,CAAC;YACD,4FAA4F;YAC5F,IAAI,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,YAAY,CAAC,GAAG,CAAC,CAAA;YAC7B,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC,CAAA;YACvE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,IAAI,aAAa,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAA;YACjD,CAAC;YACD,MAAM,CAAC,SAAS,EAAE,GAAG,YAAY,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACpE,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YAEzC,0FAA0F;YAC1F,4FAA4F;YAC5F,8CAA8C;YAC9C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAA;YACjE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACb,MAAM,IAAI,aAAa,CAAC,kBAAkB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;YAC9D,CAAC;YAED,2FAA2F;YAC3F,8EAA8E;YAC9E,IAAI,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAA;YACxC,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,mBAAmB,SAAS,MAAM,OAAO,EAAE,CAAC,CAAC,CAAA;YAC5F,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,IAAI,aAAa,CAAC,yBAAyB,CAAC,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;YACvF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAA;YAChC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;gBACjB,MAAM,IAAI,aAAa,CAAC,gBAAgB,CAAC,CAAA;YAC3C,CAAC;YAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QACzB,CAAC;QAED,8FAA8F;QAC9F,wFAAwF;QACxF,4FAA4F;QAC5F,gDAAgD;QAChD,KAAK,cAAc,CAAC,SAAS,CAAC;QAC9B,KAAK,cAAc,CAAC,IAAI,CAAC;QACzB,KAAK,cAAc,CAAC,IAAI;YACtB,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,CAAA;QAE5G,yFAAyF;QACzF,uFAAuF;QACvF,kFAAkF;QAClF,KAAK,cAAc,CAAC,KAAK;YACvB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAA;QAEzE;YACE,MAAM,IAAI,aAAa,CAAC,wBAAwB,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACtE,CAAC;AACH,CAAC,CAAA;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the target's own health endpoint, and deciding what its answer means.
|
|
3
|
+
*
|
|
4
|
+
* Two callers need it and they must agree: the boot check, which waits for a boot to resolve one
|
|
5
|
+
* way or the other, and the local run's status, which is the only thing that ever notices a target
|
|
6
|
+
* that came up and then stopped being able to serve. The classifier is pure and the IO is a thin
|
|
7
|
+
* wrapper over it.
|
|
8
|
+
*/
|
|
9
|
+
/** The shape a target's `/api/healtz` answers with. Every field is optional by contract. */
|
|
10
|
+
export interface TargetHealthBody {
|
|
11
|
+
phase?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
bootId?: string;
|
|
14
|
+
db?: {
|
|
15
|
+
ok?: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
summary?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The target's own verdict on its job queue store, reported exactly like `db`.
|
|
21
|
+
*
|
|
22
|
+
* Absent from every target that has no queue — most of them — so an omitted value means
|
|
23
|
+
* "nothing to report", never "unhealthy".
|
|
24
|
+
*/
|
|
25
|
+
valkey?: {
|
|
26
|
+
ok?: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
summary?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface TargetHealthInput {
|
|
32
|
+
/** Boot id handed to the current child; '' when none was minted. */
|
|
33
|
+
bootId: string;
|
|
34
|
+
/** Whether a TCP connect to the port succeeded at all. */
|
|
35
|
+
listening: boolean;
|
|
36
|
+
/** HTTP status of the answer; null when the request never completed. */
|
|
37
|
+
status: number | null;
|
|
38
|
+
/** Parsed body, or null when there was none or it was not JSON. */
|
|
39
|
+
body: TargetHealthBody | null;
|
|
40
|
+
/** Why the request failed, when it did. */
|
|
41
|
+
failure?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface TargetHealthReading {
|
|
44
|
+
/** The target says it is serving. */
|
|
45
|
+
ready: boolean;
|
|
46
|
+
/** No amount of waiting changes this answer — the caller must act. */
|
|
47
|
+
terminal: boolean;
|
|
48
|
+
/** Human-readable cause, '' when ready. */
|
|
49
|
+
reason: string;
|
|
50
|
+
/** The answer came from a process this connector did not start. */
|
|
51
|
+
foreign: boolean;
|
|
52
|
+
/** The phase the target reported, when it reported one. */
|
|
53
|
+
phase?: string;
|
|
54
|
+
dbOk?: boolean;
|
|
55
|
+
valkeyOk?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/** Where the api answers. The worker has a health port but no api base in front of it. */
|
|
58
|
+
export declare const TARGET_HEALTH_PATH = "/api/healtz";
|
|
59
|
+
/**
|
|
60
|
+
* What a target's answer means.
|
|
61
|
+
*
|
|
62
|
+
* The order of these checks is the contract, not a style choice:
|
|
63
|
+
*
|
|
64
|
+
* 1. **Identity outranks health.** A leftover process answering `200 ok` is still a leftover, and
|
|
65
|
+
* everything it reports describes the build IT was started with. Terminal, because waiting
|
|
66
|
+
* never turns another process's health into ours — the port has to be taken back.
|
|
67
|
+
* 2. **A non-2xx is a failure even when the body cannot be parsed.** This is what made a target
|
|
68
|
+
* answering 503 with an HTML error page read as *ready*: an unparseable body left `phase`
|
|
69
|
+
* undefined, and undefined meant "an old target that only listens once it is up".
|
|
70
|
+
* 3. **A target that says it failed is terminal**, and its own message is the reason.
|
|
71
|
+
* 4. **A database the target cannot reach is a failure it will not report any other way.** It
|
|
72
|
+
* answers `200 {status:'OK'}` with `db.ok: false`, which by rule 2's fallback would also have
|
|
73
|
+
* read as ready. Not terminal: a database that went away can come back.
|
|
74
|
+
* 5. **A queue store it cannot reach is the same failure with a different dependency** — an app
|
|
75
|
+
* whose jobs cannot be enqueued serves pages and silently does none of the work behind them.
|
|
76
|
+
* 6. Only then does a missing phase mean "an older target, and a reachable port is the answer".
|
|
77
|
+
*/
|
|
78
|
+
export declare const classifyTargetHealth: (input: TargetHealthInput) => TargetHealthReading;
|
|
79
|
+
export interface TargetHealthProbes {
|
|
80
|
+
probe?: (port: number) => Promise<boolean>;
|
|
81
|
+
fetch?: typeof globalThis.fetch;
|
|
82
|
+
/** The worker answers the same contract at a path of its own. */
|
|
83
|
+
path?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Ask the target how it is, and classify the answer.
|
|
87
|
+
*
|
|
88
|
+
* The TCP probe runs first and short-circuits: there is no point spending a request timeout on a
|
|
89
|
+
* port nothing is listening on, and a target mid-restart is exactly when this is called most.
|
|
90
|
+
*/
|
|
91
|
+
export declare const readTargetHealth: (port: number, bootId: string, timeoutMs: number, probes?: TargetHealthProbes) => Promise<TargetHealthReading>;
|
|
92
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/executor/health.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,4FAA4F;AAC5F,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,EAAE,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACvD;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5D;AAED,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,SAAS,EAAE,OAAO,CAAA;IAClB,wEAAwE;IACxE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,mEAAmE;IACnE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC7B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAA;IACd,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAA;IACjB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,0FAA0F;AAC1F,eAAO,MAAM,kBAAkB,gBAA+B,CAAA;AAQ9D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,oBAAoB,UAAW,iBAAiB,KAAG,mBA8D/D,CAAA;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1C,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAC/B,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,SACrB,MAAM,UAAU,MAAM,aAAa,MAAM,WAAU,kBAAkB,KAC1E,OAAO,CAAC,mBAAmB,CAwB7B,CAAA"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { TARGET_API_BASE } from '@owlmeans/viable-common';
|
|
2
|
+
import { probeListening } from './spawn.js';
|
|
3
|
+
/** Where the api answers. The worker has a health port but no api base in front of it. */
|
|
4
|
+
export const TARGET_HEALTH_PATH = `/${TARGET_API_BASE}/healtz`;
|
|
5
|
+
const ok = () => ({ ready: true, terminal: false, reason: '', foreign: false });
|
|
6
|
+
const not = (reason, terminal = false, extra = {}) => ({ ready: false, terminal, reason, foreign: false, ...extra });
|
|
7
|
+
/**
|
|
8
|
+
* What a target's answer means.
|
|
9
|
+
*
|
|
10
|
+
* The order of these checks is the contract, not a style choice:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Identity outranks health.** A leftover process answering `200 ok` is still a leftover, and
|
|
13
|
+
* everything it reports describes the build IT was started with. Terminal, because waiting
|
|
14
|
+
* never turns another process's health into ours — the port has to be taken back.
|
|
15
|
+
* 2. **A non-2xx is a failure even when the body cannot be parsed.** This is what made a target
|
|
16
|
+
* answering 503 with an HTML error page read as *ready*: an unparseable body left `phase`
|
|
17
|
+
* undefined, and undefined meant "an old target that only listens once it is up".
|
|
18
|
+
* 3. **A target that says it failed is terminal**, and its own message is the reason.
|
|
19
|
+
* 4. **A database the target cannot reach is a failure it will not report any other way.** It
|
|
20
|
+
* answers `200 {status:'OK'}` with `db.ok: false`, which by rule 2's fallback would also have
|
|
21
|
+
* read as ready. Not terminal: a database that went away can come back.
|
|
22
|
+
* 5. **A queue store it cannot reach is the same failure with a different dependency** — an app
|
|
23
|
+
* whose jobs cannot be enqueued serves pages and silently does none of the work behind them.
|
|
24
|
+
* 6. Only then does a missing phase mean "an older target, and a reachable port is the answer".
|
|
25
|
+
*/
|
|
26
|
+
export const classifyTargetHealth = (input) => {
|
|
27
|
+
if (!input.listening) {
|
|
28
|
+
return not('The target backend is running but never bound its port');
|
|
29
|
+
}
|
|
30
|
+
if (input.failure != null) {
|
|
31
|
+
// A timeout is materially different from a refused connection: the port answered, so
|
|
32
|
+
// something is there — it just cannot serve its own health endpoint. Say which.
|
|
33
|
+
const timedOut = /timeout|aborted/i.test(input.failure);
|
|
34
|
+
return not(timedOut
|
|
35
|
+
? 'The target backend bound its port but its health endpoint did not answer in time'
|
|
36
|
+
: `The target backend health check failed: ${input.failure}`);
|
|
37
|
+
}
|
|
38
|
+
const body = input.body;
|
|
39
|
+
if (input.bootId !== '' && typeof body?.bootId === 'string' && body.bootId !== ''
|
|
40
|
+
&& body.bootId !== input.bootId) {
|
|
41
|
+
return not('The backend port is held by a leftover process running an older build', true, { foreign: true, phase: body.phase });
|
|
42
|
+
}
|
|
43
|
+
if (input.status != null && (input.status < 200 || input.status >= 300)) {
|
|
44
|
+
const detail = body?.error ?? body?.db?.error;
|
|
45
|
+
return not(`The target backend health endpoint answered ${input.status}`
|
|
46
|
+
+ (detail != null && detail !== '' ? `: ${detail}` : ''), false, { phase: body?.phase });
|
|
47
|
+
}
|
|
48
|
+
if (body?.phase === 'failed') {
|
|
49
|
+
const reason = body.error ?? body.db?.error ?? body.valkey?.error ?? 'unknown error';
|
|
50
|
+
return not(`The target backend failed to initialize: ${reason}`, true, {
|
|
51
|
+
phase: body.phase, dbOk: body.db?.ok, valkeyOk: body.valkey?.ok,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (body?.db?.ok === false) {
|
|
55
|
+
const detail = body.db.error ?? body.db.summary ?? 'unknown error';
|
|
56
|
+
return not(`The target backend cannot reach its database: ${detail}`, false, {
|
|
57
|
+
phase: body.phase, dbOk: false, valkeyOk: body.valkey?.ok,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (body?.valkey?.ok === false) {
|
|
61
|
+
const detail = body.valkey.error ?? body.valkey.summary ?? 'unknown error';
|
|
62
|
+
return not(`The target backend cannot reach its queue store: ${detail}`, false, {
|
|
63
|
+
phase: body.phase, dbOk: body.db?.ok, valkeyOk: false,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (body?.phase == null || body.phase === 'ready') {
|
|
67
|
+
return { ...ok(), phase: body?.phase, dbOk: body?.db?.ok, valkeyOk: body?.valkey?.ok };
|
|
68
|
+
}
|
|
69
|
+
return not('The target backend is still initializing', false, {
|
|
70
|
+
phase: body.phase, dbOk: body.db?.ok, valkeyOk: body.valkey?.ok,
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Ask the target how it is, and classify the answer.
|
|
75
|
+
*
|
|
76
|
+
* The TCP probe runs first and short-circuits: there is no point spending a request timeout on a
|
|
77
|
+
* port nothing is listening on, and a target mid-restart is exactly when this is called most.
|
|
78
|
+
*/
|
|
79
|
+
export const readTargetHealth = async (port, bootId, timeoutMs, probes = {}) => {
|
|
80
|
+
const probe = probes.probe ?? probeListening;
|
|
81
|
+
const doFetch = probes.fetch ?? globalThis.fetch;
|
|
82
|
+
if (!await probe(port)) {
|
|
83
|
+
return classifyTargetHealth({ bootId, listening: false, status: null, body: null });
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const response = await doFetch(`http://127.0.0.1:${port}${probes.path ?? TARGET_HEALTH_PATH}`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
87
|
+
const body = await response.json().catch(() => null);
|
|
88
|
+
return classifyTargetHealth({ bootId, listening: true, status: response.status, body });
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
const error = e;
|
|
92
|
+
return classifyTargetHealth({
|
|
93
|
+
bootId, listening: true, status: null, body: null,
|
|
94
|
+
failure: error?.name === 'TimeoutError' ? 'TimeoutError' : String(error?.message ?? e),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/executor/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SlotCommandPayload, TargetIntegrityReport, TargetPaths } from '@owlmeans/viable-common';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './layout.js';
|
|
4
|
+
export * from './integrity.js';
|
|
5
|
+
export * from './files.js';
|
|
6
|
+
export * from './env.js';
|
|
7
|
+
export * from './health.js';
|
|
8
|
+
export * from './boot-check.js';
|
|
9
|
+
export * from './shell.js';
|
|
10
|
+
export * from './git.js';
|
|
11
|
+
export { BUILD_TIMEOUT, MAX_OUTPUT_BUFFER, failureOutput, isAlive, killGroupAndWait, probeListening, runCommand, runScript, signalGroup, waitForPortFree, withDeadline } from './spawn.js';
|
|
12
|
+
export interface LocalSlotExecutorOptions {
|
|
13
|
+
/** Where the executor's own diagnostics go. NEVER stdout for a stdio MCP server. */
|
|
14
|
+
log?: (line: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface LocalSlotExecutor {
|
|
17
|
+
/** Answer one slot command against the local tree. */
|
|
18
|
+
execute: (payload: SlotCommandPayload) => Promise<unknown>;
|
|
19
|
+
/** Which tree this directory holds, re-read per call. */
|
|
20
|
+
layout: () => TargetPaths;
|
|
21
|
+
/** Whether the tree is still the generated application. */
|
|
22
|
+
integrity: () => Promise<TargetIntegrityReport>;
|
|
23
|
+
dir: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Answer the platform's slot commands against a directory on this machine.
|
|
27
|
+
*
|
|
28
|
+
* The dispatcher is the publisher's, switch for switch, and that is the point: the platform's
|
|
29
|
+
* remote helpers parse the ANSWERS, and they have no way to know whether a pod or a laptop
|
|
30
|
+
* produced one. So an ordinary failure is never thrown here — it comes back in the same shape the
|
|
31
|
+
* publisher would have sent, error text and all — and only an unknown command, which is a
|
|
32
|
+
* protocol fault rather than a project fault, escapes as an exception.
|
|
33
|
+
*
|
|
34
|
+
* Everything is resolved per call rather than closed over: a re-initialization replaces the tree
|
|
35
|
+
* under a running connector, and a remembered layout would then resolve every path into a
|
|
36
|
+
* directory that no longer exists.
|
|
37
|
+
*/
|
|
38
|
+
export declare const makeLocalSlotExecutor: (dir: string, opts?: LocalSlotExecutorOptions) => LocalSlotExecutor;
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/executor/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAAoD,qBAAqB,EAC3F,WAAW,EACZ,MAAM,yBAAyB,CAAA;AAShC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,OAAO,EACL,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAC1F,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAClE,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,wBAAwB;IACvC,oFAAoF;IACpF,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,OAAO,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1D,yDAAyD;IACzD,MAAM,EAAE,MAAM,WAAW,CAAA;IACzB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAA;IAC/C,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,QAC3B,MAAM,SAAQ,wBAAwB,KAC1C,iBA4LF,CAAA"}
|