mainflow 0.35.0 → 0.37.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/dist/cli.js +31 -6
- package/dist/{dist-BdvWGTsb.js → dist-ud09dzZo.js} +14 -2
- package/dist/facts.d.ts +7 -7
- package/dist/facts.js +9 -9
- package/dist/grant-commands.d.ts +1 -1
- package/dist/grant-commands.js +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +20 -3
- package/dist/{host-bnHYIwuY.js → host-lY8ytTbw.js} +330 -30
- package/dist/host.js +1 -1
- package/dist/{index-DgYB5u1f.d.ts → index-B32s8PHu.d.ts} +5 -1
- package/dist/{launch-D80CYPcm.js → launch-3HLHjQF0.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/native/mainflow-swap-darwin-arm64 +0 -0
- package/dist/native/mainflow-swap-darwin-x64 +0 -0
- package/dist/{operation-DVcJgKM9.js → operation-BZ7CHB6J.js} +1 -1
- package/dist/{ui-C4h4iLDu.js → ui-AhBsqtZe.js} +13 -6
- package/dist/web/assets/{index-BDnlbKcr.js → index-8SRwJNYs.js} +4 -4
- package/dist/web/assets/index-CuHEjqof.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-Z6UfQ6pW.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as OFFSET, A as SURVEY_SECTION, B as issueSchema, C as REF_EXAMPLES, D as SESSION_STATUS, E as SESSION_ID, F as configSchema, G as parseRef, H as opsEntrySchema, I as decisionSchema, J as refMainline, K as parseUntil, L as findingSchema, M as TASK_SECTIONS, O as SLUG, P as WORK_STATUS, Q as waitingForRef, R as formatOpsEntry, S as PRIORITIES, T as SERVES_REF, U as parseActionRef, V as mainlineSchema, W as parseOpsLine, X as taskSchema, Y as sessionSchema, Z as waitingForAction, a as DECISION_SECTIONS, at as grantCovers, b as MAINLINE_STATUS, ct as parseGrantTarget, et as stampMs, f as GROUP_SECTIONS, h as ISSUE_SECTIONS, i as DECISION_BASES, it as describeRequest, j as TASK_REQUIRED_SECTIONS, k as SURVEY_LINES, l as FINDING_SECTIONS, n as CLOSED_STATUS, ot as grantForm, p as GROUP_STATUS, q as refExample, r as DECIDERS, s as DEFAULT_CATEGORIES, st as grantRepo, u as FINDING_STATUS, v as MAINLINE_ROLES, w as RESERVED_HUB_NAMES, x as MERGE_STATES, y as MAINLINE_SECTIONS, z as groupSchema } from "./dist-
|
|
1
|
+
import { $ as OFFSET, A as SURVEY_SECTION, B as issueSchema, C as REF_EXAMPLES, D as SESSION_STATUS, E as SESSION_ID, F as configSchema, G as parseRef, H as opsEntrySchema, I as decisionSchema, J as refMainline, K as parseUntil, L as findingSchema, M as TASK_SECTIONS, N as UNTIL_FORMS, O as SLUG, P as WORK_STATUS, Q as waitingForRef, R as formatOpsEntry, S as PRIORITIES, T as SERVES_REF, U as parseActionRef, V as mainlineSchema, W as parseOpsLine, X as taskSchema, Y as sessionSchema, Z as waitingForAction, a as DECISION_SECTIONS, at as grantCovers, b as MAINLINE_STATUS, ct as parseGrantTarget, et as stampMs, f as GROUP_SECTIONS, h as ISSUE_SECTIONS, i as DECISION_BASES, it as describeRequest, j as TASK_REQUIRED_SECTIONS, k as SURVEY_LINES, l as FINDING_SECTIONS, n as CLOSED_STATUS, ot as grantForm, p as GROUP_STATUS, q as refExample, r as DECIDERS, s as DEFAULT_CATEGORIES, st as grantRepo, u as FINDING_STATUS, v as MAINLINE_ROLES, w as RESERVED_HUB_NAMES, x as MERGE_STATES, y as MAINLINE_SECTIONS, z as groupSchema } from "./dist-ud09dzZo.js";
|
|
2
2
|
import { a as LABEL_ROLES, i as FINDING_SEVERITIES, n as FINDING_CLASSES, r as FINDING_FIELDS, t as FINDINGS_FENCE } from "./retro-formats-ECy0gGOL.js";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { appendFileSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, symlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
@@ -137,6 +137,91 @@ const offlineIds = {
|
|
|
137
137
|
next: (request) => fresh(request, true)
|
|
138
138
|
};
|
|
139
139
|
//#endregion
|
|
140
|
+
//#region ../core/dist/ops/unknown.js
|
|
141
|
+
const isPlain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
142
|
+
const idOf = (value) => isPlain(value) && typeof value.id === "string" ? value.id : void 0;
|
|
143
|
+
/** Keys that would reach the prototype instead of the data when assigned. */
|
|
144
|
+
const UNSAFE_KEYS = /* @__PURE__ */ new Set([
|
|
145
|
+
"__proto__",
|
|
146
|
+
"constructor",
|
|
147
|
+
"prototype"
|
|
148
|
+
]);
|
|
149
|
+
function diff(raw, parsed) {
|
|
150
|
+
if (isPlain(raw) && isPlain(parsed)) {
|
|
151
|
+
const fields = /* @__PURE__ */ new Map();
|
|
152
|
+
for (const [key, value] of Object.entries(raw)) if (!Object.hasOwn(parsed, key)) fields.set(key, {
|
|
153
|
+
kind: "value",
|
|
154
|
+
value
|
|
155
|
+
});
|
|
156
|
+
else {
|
|
157
|
+
const inner = diff(value, parsed[key]);
|
|
158
|
+
if (inner) fields.set(key, inner);
|
|
159
|
+
}
|
|
160
|
+
return fields.size > 0 ? {
|
|
161
|
+
kind: "object",
|
|
162
|
+
fields
|
|
163
|
+
} : void 0;
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(raw) && Array.isArray(parsed)) {
|
|
166
|
+
const counts = /* @__PURE__ */ new Map();
|
|
167
|
+
for (const element of raw) {
|
|
168
|
+
const id = idOf(element);
|
|
169
|
+
if (id !== void 0) counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
170
|
+
}
|
|
171
|
+
const byId = /* @__PURE__ */ new Map();
|
|
172
|
+
for (const element of raw) {
|
|
173
|
+
const id = idOf(element);
|
|
174
|
+
if (id === void 0 || counts.get(id) !== 1) continue;
|
|
175
|
+
const matches = parsed.filter((p) => idOf(p) === id);
|
|
176
|
+
if (matches.length !== 1) continue;
|
|
177
|
+
const inner = diff(element, matches[0]);
|
|
178
|
+
if (inner) byId.set(id, inner);
|
|
179
|
+
}
|
|
180
|
+
return byId.size > 0 ? {
|
|
181
|
+
kind: "array",
|
|
182
|
+
byId
|
|
183
|
+
} : void 0;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** The parts of `raw`, as it was read, that validation left out of `parsed`; undefined when nothing was dropped. */
|
|
187
|
+
function strippedOf(raw, parsed) {
|
|
188
|
+
return diff(raw, parsed);
|
|
189
|
+
}
|
|
190
|
+
function put(target, stripped) {
|
|
191
|
+
if (stripped.kind === "object" && isPlain(target)) for (const [key, inner] of stripped.fields) {
|
|
192
|
+
if (UNSAFE_KEYS.has(key)) {
|
|
193
|
+
if (inner.kind === "value" && !Object.hasOwn(target, key)) Object.defineProperty(target, key, {
|
|
194
|
+
value: structuredClone(inner.value),
|
|
195
|
+
enumerable: true,
|
|
196
|
+
writable: true,
|
|
197
|
+
configurable: true
|
|
198
|
+
});
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (inner.kind === "value") {
|
|
202
|
+
if (!Object.hasOwn(target, key)) target[key] = structuredClone(inner.value);
|
|
203
|
+
} else if (Object.hasOwn(target, key)) put(target[key], inner);
|
|
204
|
+
}
|
|
205
|
+
else if (stripped.kind === "array" && Array.isArray(target)) {
|
|
206
|
+
const counts = /* @__PURE__ */ new Map();
|
|
207
|
+
for (const element of target) {
|
|
208
|
+
const id = idOf(element);
|
|
209
|
+
if (id !== void 0) counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
210
|
+
}
|
|
211
|
+
for (const element of target) {
|
|
212
|
+
const id = idOf(element);
|
|
213
|
+
const inner = id === void 0 || counts.get(id) !== 1 ? void 0 : stripped.byId.get(id);
|
|
214
|
+
if (inner) put(element, inner);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** A copy of `next` with the stripped parts put back where the data has no value of its own. */
|
|
219
|
+
function graft(next, stripped) {
|
|
220
|
+
const copy = structuredClone(next);
|
|
221
|
+
if (stripped) put(copy, stripped);
|
|
222
|
+
return copy;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
140
225
|
//#region ../core/dist/ops/draft.js
|
|
141
226
|
const normalize$1 = (path) => path.split("/").filter(Boolean).join("/");
|
|
142
227
|
const parentOf = (path) => normalize$1(path).split("/").slice(0, -1).join("/");
|
|
@@ -263,7 +348,11 @@ function commit(storage, changes) {
|
|
|
263
348
|
case "remove": storage.remove(change.path);
|
|
264
349
|
}
|
|
265
350
|
}
|
|
266
|
-
/**
|
|
351
|
+
/**
|
|
352
|
+
* Run an operation on the hub as it is under the lock and apply its changes before the lock is released. It does not
|
|
353
|
+
* check the hub's data format: migrate must write a hub of another format, and the sync server's text patches
|
|
354
|
+
* keep every field. The entries for operations (`runOp`, `applyOperation`) check it before they call this.
|
|
355
|
+
*/
|
|
267
356
|
function applyOp(storage, run) {
|
|
268
357
|
return storage.lock(() => {
|
|
269
358
|
const result = run(storage);
|
|
@@ -288,14 +377,20 @@ function readYamlIn(hub, path, schema) {
|
|
|
288
377
|
function readFrontMatterIn(hub, path, schema) {
|
|
289
378
|
return validate(schema, splitFrontMatter(requireText(hub, path)).data, hub.where(path));
|
|
290
379
|
}
|
|
380
|
+
/** `next` with the fields the YAML document at `path` has beyond its schema put back; `current` is that document as validated. */
|
|
381
|
+
function yamlKeeping(hub, path, current, next) {
|
|
382
|
+
return graft(next, strippedOf(parse$1(requireText(hub, path)), current));
|
|
383
|
+
}
|
|
291
384
|
/** Mutate, validate and stage a YAML document; `expectedVersion` refuses the change when the file moved on. */
|
|
292
385
|
function editYaml(draft, path, schema, mutate, expectedVersion) {
|
|
293
386
|
const text = requireText(draft, path);
|
|
294
387
|
checkVersion(draft, path, text, expectedVersion);
|
|
295
|
-
const
|
|
388
|
+
const raw = parse$1(text);
|
|
389
|
+
const data = validate(schema, raw, draft.where(path));
|
|
390
|
+
const stripped = strippedOf(raw, data);
|
|
296
391
|
mutate(data);
|
|
297
392
|
const next = validate(schema, data, draft.where(path));
|
|
298
|
-
draft.write(path, toYaml(next));
|
|
393
|
+
draft.write(path, toYaml(graft(next, stripped)));
|
|
299
394
|
return next;
|
|
300
395
|
}
|
|
301
396
|
/** Mutate, validate and stage the front matter of a Markdown document; the body is kept unless `mutate` returns a new one. */
|
|
@@ -304,9 +399,10 @@ function editFrontMatter(draft, path, schema, mutate, expectedVersion) {
|
|
|
304
399
|
checkVersion(draft, path, text, expectedVersion);
|
|
305
400
|
const { data, body } = splitFrontMatter(text);
|
|
306
401
|
const current = validate(schema, data, draft.where(path));
|
|
402
|
+
const stripped = strippedOf(data, current);
|
|
307
403
|
const nextBody = mutate(current, body) ?? body;
|
|
308
404
|
const next = validate(schema, current, draft.where(path));
|
|
309
|
-
draft.write(path, frontMatterText(next, nextBody));
|
|
405
|
+
draft.write(path, frontMatterText(graft(next, stripped), nextBody));
|
|
310
406
|
return next;
|
|
311
407
|
}
|
|
312
408
|
//#endregion
|
|
@@ -364,6 +460,40 @@ function decisionFileIn(hub, mainline, decision) {
|
|
|
364
460
|
}
|
|
365
461
|
const sessionFileIn = (id) => `${SESSIONS_DIR}/${id}.yaml`;
|
|
366
462
|
//#endregion
|
|
463
|
+
//#region ../core/dist/ops/format.js
|
|
464
|
+
/** Why a hub whose data format is `found` cannot be written by this CLI; null when it can. */
|
|
465
|
+
function formatMismatch(found) {
|
|
466
|
+
if (found === 3) return null;
|
|
467
|
+
return found < 3 ? `data format version ${found} is older than this CLI's (3); run "mainflow migrate --dry-run" to preview the conversion, then "mainflow migrate"` : `data format version ${found} is newer than this CLI supports (3); install the matching mainflow version in this hub`;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* The data format config.yaml declares; null when the hub has no config.yaml or it names no number, which loading
|
|
471
|
+
* the config reports. A config.yaml that is not YAML is a WriteError, so the sync server refuses the request
|
|
472
|
+
* instead of failing on it.
|
|
473
|
+
*/
|
|
474
|
+
function hubFormatVersion(hub) {
|
|
475
|
+
const text = hub.read(CONFIG_FILE);
|
|
476
|
+
if (text === null) return null;
|
|
477
|
+
let data;
|
|
478
|
+
try {
|
|
479
|
+
data = parse$1(text);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
throw new WriteError(`refusing to write: ${hub.where(CONFIG_FILE)} cannot be parsed as YAML: ${error.message}`);
|
|
482
|
+
}
|
|
483
|
+
const found = data?.formatVersion;
|
|
484
|
+
return typeof found === "number" ? found : null;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Refuse a write to a hub whose data format is not the one this CLI writes: rewriting a document would drop what
|
|
488
|
+
* another format keeps there. A hub without config.yaml is not checked, since `init` writes it.
|
|
489
|
+
*/
|
|
490
|
+
function requireWritableFormat(hub) {
|
|
491
|
+
const found = hubFormatVersion(hub);
|
|
492
|
+
if (found === null) return;
|
|
493
|
+
const reason = formatMismatch(found);
|
|
494
|
+
if (reason) throw new WriteError(`refusing to write: ${reason}`);
|
|
495
|
+
}
|
|
496
|
+
//#endregion
|
|
367
497
|
//#region ../core/dist/ops/protocol.js
|
|
368
498
|
/** `now` of a request: a local minute and its offset from UTC, `Z` for UTC itself. */
|
|
369
499
|
const REQUEST_TIME = new RegExp(`^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})(${OFFSET})$`);
|
|
@@ -584,7 +714,10 @@ const localContext = () => ({ now: nowMinute() });
|
|
|
584
714
|
/** Run an operation on the hub at `root` under its write lock, apply its changes and return what it reports. */
|
|
585
715
|
function runOp(root, run) {
|
|
586
716
|
const ctx = localContext();
|
|
587
|
-
return applyOp(nodeStorage(root), (hub) =>
|
|
717
|
+
return applyOp(nodeStorage(root), (hub) => {
|
|
718
|
+
requireWritableFormat(hub);
|
|
719
|
+
return run(hub, ctx);
|
|
720
|
+
}).value;
|
|
588
721
|
}
|
|
589
722
|
/** Run an operation that must not take the lock, such as one creating the hub the lock would live in. */
|
|
590
723
|
function runUnlocked(root, run) {
|
|
@@ -618,6 +751,7 @@ const mainlineDir = (root, id) => onDisk(root, mainlineDirIn(nodeStorage(root),
|
|
|
618
751
|
const taskDir = (root, mainline, task) => onDisk(root, taskDirIn(nodeStorage(root), mainline, task));
|
|
619
752
|
const mainlineFile = (root, id) => join(mainlineDir(root, id), MAINLINE_FILE);
|
|
620
753
|
const taskFile = (root, mainline, task) => join(taskDir(root, mainline, task), TASK_FILE);
|
|
754
|
+
const readConfig = (root) => readConfigIn(nodeStorage(root));
|
|
621
755
|
//#endregion
|
|
622
756
|
//#region ../core/dist/refs.js
|
|
623
757
|
const itemStatus = (item) => item.done ? "done" : item.dropped ? "dropped" : "open";
|
|
@@ -706,6 +840,64 @@ function linksInto(hub, hubName) {
|
|
|
706
840
|
for (const g of hub.groups) add(g.id, g.links);
|
|
707
841
|
return out;
|
|
708
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* Where a member's `group` value points: `id` is the group's id, and `hub` is the other hub's name, or undefined
|
|
845
|
+
* for a group of the member's own hub (no prefix, or the member's own hub name as prefix).
|
|
846
|
+
*/
|
|
847
|
+
function splitGroup(value, ownHub) {
|
|
848
|
+
const colon = value.indexOf(":");
|
|
849
|
+
if (colon < 0) return { id: value };
|
|
850
|
+
const hub = value.slice(0, colon);
|
|
851
|
+
const id = value.slice(colon + 1);
|
|
852
|
+
return hub === ownHub ? { id } : {
|
|
853
|
+
hub,
|
|
854
|
+
id
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
/** Whether a member's `group` value names the group `groupId` of its own hub `ownHub`: `w001` or `{ownHub}:w001`. */
|
|
858
|
+
function memberOfGroup(group, groupId, ownHub) {
|
|
859
|
+
if (group === void 0) return false;
|
|
860
|
+
const target = splitGroup(group, ownHub);
|
|
861
|
+
return target.hub === void 0 && target.id === groupId;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* The mainlines and issues of the registered hubs whose `group` names one of `hubName`'s groups, by group id. Only
|
|
865
|
+
* the hubs' own documents are read, one level, so a hub that is not on this machine contributes nothing.
|
|
866
|
+
*/
|
|
867
|
+
function remoteMembers(remotes, hubName) {
|
|
868
|
+
const out = /* @__PURE__ */ new Map();
|
|
869
|
+
const add = (group, member) => {
|
|
870
|
+
if (!group) return;
|
|
871
|
+
const target = splitGroup(group, member.hub);
|
|
872
|
+
if (target.hub !== hubName) return;
|
|
873
|
+
out.set(target.id, [...out.get(target.id) ?? [], member]);
|
|
874
|
+
};
|
|
875
|
+
for (const r of remotes) {
|
|
876
|
+
if (!r.hub) continue;
|
|
877
|
+
for (const m of r.hub.mainlines) add(m.group, {
|
|
878
|
+
ref: `${r.name}:${m.id}`,
|
|
879
|
+
hub: r.name,
|
|
880
|
+
kind: "mainline",
|
|
881
|
+
title: m.title,
|
|
882
|
+
status: m.status,
|
|
883
|
+
role: m.role ?? "impl",
|
|
884
|
+
archived: Boolean(m.archivedAt),
|
|
885
|
+
open: !CLOSED_STATUS.mainline.includes(m.status)
|
|
886
|
+
});
|
|
887
|
+
for (const i of r.hub.issues) add(i.group, {
|
|
888
|
+
ref: `${r.name}:${i.id}`,
|
|
889
|
+
hub: r.name,
|
|
890
|
+
kind: "issue",
|
|
891
|
+
title: i.title,
|
|
892
|
+
status: i.status,
|
|
893
|
+
archived: Boolean(i.archivedAt),
|
|
894
|
+
open: i.status !== "closed"
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return out;
|
|
898
|
+
}
|
|
899
|
+
/** The registered hubs that could not be loaded, for a reader that must say its member list may be incomplete. */
|
|
900
|
+
const unavailableHubs = (remotes) => remotes.filter((r) => r.unavailable).map((r) => r.name);
|
|
709
901
|
//#endregion
|
|
710
902
|
//#region ../core/dist/deps.js
|
|
711
903
|
const MET = /* @__PURE__ */ new Set([
|
|
@@ -972,7 +1164,8 @@ function loadConfig(root) {
|
|
|
972
1164
|
message: issue.message
|
|
973
1165
|
})));
|
|
974
1166
|
const config = result.data;
|
|
975
|
-
|
|
1167
|
+
const mismatch = formatMismatch(config.formatVersion);
|
|
1168
|
+
if (mismatch) throw new HubError(mismatch, []);
|
|
976
1169
|
return config;
|
|
977
1170
|
}
|
|
978
1171
|
function loadHub(root, options = {}) {
|
|
@@ -1165,8 +1358,7 @@ function loadHub(root, options = {}) {
|
|
|
1165
1358
|
}
|
|
1166
1359
|
const findings = hubDocs(join(dataDir, "findings"), findingSchema);
|
|
1167
1360
|
const groups = hubDocs(join(dataDir, "groups"), groupSchema);
|
|
1168
|
-
problems.push(...groupProblems(groups, mainlines, hubIssues, unreadable));
|
|
1169
|
-
warnings.push(...groupWarnings(groups, mainlines, hubIssues));
|
|
1361
|
+
problems.push(...groupProblems(config.hub, groups, mainlines, hubIssues, unreadable));
|
|
1170
1362
|
const ops = [];
|
|
1171
1363
|
const opsPath = join(dataDir, "ops");
|
|
1172
1364
|
for (const name of existsSync(opsPath) ? readdirSync(opsPath).filter((n) => n.endsWith(".log")).sort() : []) readFileSync(join(opsPath, name), "utf8").split("\n").forEach((line, index) => {
|
|
@@ -1185,6 +1377,12 @@ function loadHub(root, options = {}) {
|
|
|
1185
1377
|
});
|
|
1186
1378
|
});
|
|
1187
1379
|
const remotes = options.remotes === false ? [] : loadRemotes(root, config, problems, warnings);
|
|
1380
|
+
warnings.push(...groupWarnings(config.hub, groups, mainlines, hubIssues, remotes));
|
|
1381
|
+
for (const doc of [...mainlines, ...hubIssues]) if (doc.group && splitGroup(doc.group, config.hub).hub !== void 0) pending.push({
|
|
1382
|
+
ref: doc.group,
|
|
1383
|
+
file: doc.file,
|
|
1384
|
+
field: "group"
|
|
1385
|
+
});
|
|
1188
1386
|
checkReferences({
|
|
1189
1387
|
config,
|
|
1190
1388
|
mainlines,
|
|
@@ -1238,20 +1436,32 @@ function loadHub(root, options = {}) {
|
|
|
1238
1436
|
remotes
|
|
1239
1437
|
};
|
|
1240
1438
|
}
|
|
1241
|
-
/** Members name their group; one that names a group this hub does not have is reported on the member. */
|
|
1242
|
-
function groupProblems(groups, mainlines, issues, unreadable) {
|
|
1439
|
+
/** Members name their group; one that names a group this hub does not have is reported on the member. A group of another hub is checked with the other references. */
|
|
1440
|
+
function groupProblems(hubName, groups, mainlines, issues, unreadable) {
|
|
1243
1441
|
const known = new Set(groups.map((g) => g.id));
|
|
1244
|
-
return [...mainlines, ...issues].flatMap((doc) =>
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1442
|
+
return [...mainlines, ...issues].flatMap((doc) => {
|
|
1443
|
+
if (!doc.group) return [];
|
|
1444
|
+
const { hub, id } = splitGroup(doc.group, hubName);
|
|
1445
|
+
return hub === void 0 && !known.has(id) && !unreadable.has(id) ? [{
|
|
1446
|
+
file: doc.file,
|
|
1447
|
+
field: "group",
|
|
1448
|
+
message: `group ${doc.group} does not exist; create it with "mainflow new group <slug> --title <title>" or name an existing one`
|
|
1449
|
+
}] : [];
|
|
1450
|
+
});
|
|
1249
1451
|
}
|
|
1250
|
-
/**
|
|
1251
|
-
|
|
1452
|
+
/**
|
|
1453
|
+
* A group marked done while some of its members are still open: a plan to settle, not a broken file. Members in
|
|
1454
|
+
* other hubs count too, when those hubs are on this machine.
|
|
1455
|
+
*/
|
|
1456
|
+
function groupWarnings(hubName, groups, mainlines, issues, remotes) {
|
|
1457
|
+
const remote = remoteMembers(remotes, hubName);
|
|
1252
1458
|
return groups.flatMap((g) => {
|
|
1253
1459
|
if (g.status !== "done") return [];
|
|
1254
|
-
const open = [
|
|
1460
|
+
const open = [
|
|
1461
|
+
...mainlines.filter((m) => memberOfGroup(m.group, g.id, hubName) && isOpen$2("mainline", m.status)).map((m) => m.id),
|
|
1462
|
+
...issues.filter((i) => memberOfGroup(i.group, g.id, hubName) && i.status !== "closed").map((i) => i.id),
|
|
1463
|
+
...(remote.get(g.id) ?? []).filter((m) => m.open).map((m) => m.ref)
|
|
1464
|
+
];
|
|
1255
1465
|
return open.length ? [{
|
|
1256
1466
|
file: g.file,
|
|
1257
1467
|
field: "status",
|
|
@@ -1648,12 +1858,18 @@ function requireExisting(hub, ref) {
|
|
|
1648
1858
|
/** A priority as a command gives it, checked against P0 to P3. */
|
|
1649
1859
|
const requirePriority = (value) => requireOneOf(value, PRIORITIES, "priority");
|
|
1650
1860
|
/**
|
|
1651
|
-
* The group a member names, which must exist
|
|
1652
|
-
*
|
|
1861
|
+
* The group a member names: one of this hub, which must exist, or one of another hub (`other-hub:w001`), which
|
|
1862
|
+
* only needs that hub registered here; the commands check the group itself when that hub is on this machine.
|
|
1863
|
+
* The hub's own name as prefix gives the group without it. `none` clears the membership; `null` is how a patch
|
|
1864
|
+
* says the same, so a command can pass the word and an interface the value.
|
|
1653
1865
|
*/
|
|
1654
1866
|
function requireGroup(hub, value) {
|
|
1655
1867
|
const ref = requireRef(value, "group");
|
|
1656
|
-
|
|
1868
|
+
const config = readConfigIn(hub);
|
|
1869
|
+
if (ref.hub !== void 0 && ref.hub !== config.hub) {
|
|
1870
|
+
if (!config.hubs?.[ref.hub]) throw new WriteError(`hub "${ref.hub}" is not registered; run "mainflow hub add ${ref.hub} <path>" first`);
|
|
1871
|
+
return `${ref.hub}:${ref.group}`;
|
|
1872
|
+
}
|
|
1657
1873
|
entryFile(hub, GROUPS_DIR, ref.group, "group");
|
|
1658
1874
|
return ref.group;
|
|
1659
1875
|
}
|
|
@@ -2509,11 +2725,11 @@ function takeUnfinished(draft, ctx, from, to, existing, at) {
|
|
|
2509
2725
|
doneAt: at
|
|
2510
2726
|
};
|
|
2511
2727
|
});
|
|
2512
|
-
draft.write(previousFile, toYaml(validate(sessionSchema, {
|
|
2728
|
+
draft.write(previousFile, toYaml(yamlKeeping(draft, previousFile, previous, validate(sessionSchema, {
|
|
2513
2729
|
...previous,
|
|
2514
2730
|
status: "handed-off",
|
|
2515
2731
|
actions: left
|
|
2516
|
-
}, draft.where(previousFile))));
|
|
2732
|
+
}, draft.where(previousFile)))));
|
|
2517
2733
|
return carried;
|
|
2518
2734
|
}
|
|
2519
2735
|
/**
|
|
@@ -2596,7 +2812,7 @@ function handoffSession(hub, input, ctx) {
|
|
|
2596
2812
|
handoffFrom: from,
|
|
2597
2813
|
actions: [...session.actions, ...carried]
|
|
2598
2814
|
}, draft.where(file));
|
|
2599
|
-
draft.write(file, toYaml(next));
|
|
2815
|
+
draft.write(file, toYaml(yamlKeeping(draft, file, session, next)));
|
|
2600
2816
|
return draft.result(next);
|
|
2601
2817
|
}
|
|
2602
2818
|
function setSessionInfo(hub, input) {
|
|
@@ -2639,10 +2855,10 @@ function settleSession(hub, input) {
|
|
|
2639
2855
|
if (s.status !== "active" || s.delegatedFrom || removed && s.id === id) continue;
|
|
2640
2856
|
if (!s.actions.every((a) => a.status === "done" || a.status === "dropped") || subagents(s.id).some((x) => x.status === "active")) continue;
|
|
2641
2857
|
if (s.id === id && s.actions.length === 0) continue;
|
|
2642
|
-
draft.write(sessionFileIn(s.id), toYaml(validate(sessionSchema, {
|
|
2858
|
+
draft.write(sessionFileIn(s.id), toYaml(yamlKeeping(draft, sessionFileIn(s.id), s, validate(sessionSchema, {
|
|
2643
2859
|
...s,
|
|
2644
2860
|
status: "ended"
|
|
2645
|
-
}, draft.where(sessionFileIn(s.id)))));
|
|
2861
|
+
}, draft.where(sessionFileIn(s.id))))));
|
|
2646
2862
|
if (s.id === id) result = "ended";
|
|
2647
2863
|
}
|
|
2648
2864
|
return draft.result(result);
|
|
@@ -2692,6 +2908,7 @@ function addAction(hub, input, ctx) {
|
|
|
2692
2908
|
* to work that has since been handed off to this very action would otherwise wait forever.
|
|
2693
2909
|
*/
|
|
2694
2910
|
function requireBlock(hub, ref, block) {
|
|
2911
|
+
if (block.until !== void 0) requireMainlineTarget(hub, block.until);
|
|
2695
2912
|
const targets = [waitingForAction(block.waitingFor), block.until === void 0 ? void 0 : untilTarget(block.until)];
|
|
2696
2913
|
for (const target of targets) {
|
|
2697
2914
|
if (!target) continue;
|
|
@@ -2700,10 +2917,23 @@ function requireBlock(hub, ref, block) {
|
|
|
2700
2917
|
if (found.ref === ref) throw new WriteError(`action ${ref} cannot wait for itself${target === ref ? "" : ` (${target} was handed off to it)`}`);
|
|
2701
2918
|
}
|
|
2702
2919
|
}
|
|
2920
|
+
/** A `mainline-done` condition has to name a mainline in this hub, or a hub registered here. */
|
|
2921
|
+
function requireMainlineTarget(hub, until) {
|
|
2922
|
+
const parsed = parseUntil(until);
|
|
2923
|
+
if (parsed?.kind !== "mainline-done") return;
|
|
2924
|
+
const ref = parseRef(parsed.mainline);
|
|
2925
|
+
if (ref?.kind !== "mainline") return;
|
|
2926
|
+
const config = readConfigIn(hub);
|
|
2927
|
+
if (ref.hub !== void 0 && ref.hub !== config.hub) {
|
|
2928
|
+
if (!config.hubs?.[ref.hub]) throw new WriteError(`hub "${ref.hub}" is not registered; run "mainflow hub add ${ref.hub} <path>" first`);
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2931
|
+
mainlineDirIn(hub, ref.mainline);
|
|
2932
|
+
}
|
|
2703
2933
|
/** The action an `until` waits for; throws when the condition matches none of the forms. */
|
|
2704
2934
|
function untilTarget(until) {
|
|
2705
2935
|
const parsed = parseUntil(until);
|
|
2706
|
-
if (!parsed) throw new WriteError(`--until takes a condition like
|
|
2936
|
+
if (!parsed) throw new WriteError(`--until takes a condition like ${UNTIL_FORMS.map((f) => f.format).join(", ")}, got "${until}"`);
|
|
2707
2937
|
return parsed.kind === "action-done" ? `${parsed.session}#${parsed.action}` : void 0;
|
|
2708
2938
|
}
|
|
2709
2939
|
function updateAction(hub, input, ctx) {
|
|
@@ -2980,7 +3210,10 @@ const OPERATIONS = {
|
|
|
2980
3210
|
function applyOperation(storage, name, input, ctx) {
|
|
2981
3211
|
const operation = OPERATIONS[name];
|
|
2982
3212
|
if (!operation) throw new Error(`unknown operation "${String(name)}"`);
|
|
2983
|
-
return applyOp(storage, (hub) =>
|
|
3213
|
+
return applyOp(storage, (hub) => {
|
|
3214
|
+
requireWritableFormat(hub);
|
|
3215
|
+
return operation(hub, input, ctx);
|
|
3216
|
+
});
|
|
2984
3217
|
}
|
|
2985
3218
|
//#endregion
|
|
2986
3219
|
//#region ../core/dist/ids/renumber.js
|
|
@@ -3917,11 +4150,78 @@ function actionState(root, ref) {
|
|
|
3917
4150
|
detail: `${ref}${via} is ${found.action.status}`
|
|
3918
4151
|
};
|
|
3919
4152
|
}
|
|
4153
|
+
/**
|
|
4154
|
+
* Whether a mainline is done. It can no longer come true when the mainline was dropped, does not exist, or its
|
|
4155
|
+
* hub is not registered here. A registered hub that is missing on this machine or fails to load may return, so
|
|
4156
|
+
* that case keeps waiting. A prefix equal to this hub's own name reads the local hub.
|
|
4157
|
+
*/
|
|
4158
|
+
function mainlineState(root, text) {
|
|
4159
|
+
const ref = parseRef(text);
|
|
4160
|
+
if (ref?.kind !== "mainline") return {
|
|
4161
|
+
met: false,
|
|
4162
|
+
unreachable: true,
|
|
4163
|
+
detail: `"${text}" is not a mainline reference like m001 or other-hub:m001`
|
|
4164
|
+
};
|
|
4165
|
+
let target = root;
|
|
4166
|
+
try {
|
|
4167
|
+
const config = readConfig(root);
|
|
4168
|
+
if (ref.hub !== void 0 && ref.hub !== config.hub) {
|
|
4169
|
+
const path = config.hubs?.[ref.hub];
|
|
4170
|
+
if (path === void 0) return {
|
|
4171
|
+
met: false,
|
|
4172
|
+
unreachable: true,
|
|
4173
|
+
detail: `hub "${ref.hub}" is not registered, so ${text} can never be read; run "mainflow hub add ${ref.hub} <path>"`
|
|
4174
|
+
};
|
|
4175
|
+
target = remoteRoot(root, path);
|
|
4176
|
+
if (!isHubRoot(target)) return {
|
|
4177
|
+
met: false,
|
|
4178
|
+
detail: `hub "${ref.hub}" is not at ${path} on this machine, so ${text} cannot be read yet`
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
} catch (error) {
|
|
4182
|
+
return {
|
|
4183
|
+
met: false,
|
|
4184
|
+
detail: `the hub config cannot be read, so ${text} cannot be checked: ${error.message}`
|
|
4185
|
+
};
|
|
4186
|
+
}
|
|
4187
|
+
try {
|
|
4188
|
+
mainlineDir(target, ref.mainline);
|
|
4189
|
+
} catch {
|
|
4190
|
+
return {
|
|
4191
|
+
met: false,
|
|
4192
|
+
unreachable: true,
|
|
4193
|
+
detail: `mainline ${text} does not exist, so it will never be done`
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
let status;
|
|
4197
|
+
try {
|
|
4198
|
+
status = readFrontMatter(mainlineFile(target, ref.mainline), mainlineSchema).data.status;
|
|
4199
|
+
} catch (error) {
|
|
4200
|
+
return {
|
|
4201
|
+
met: false,
|
|
4202
|
+
detail: `mainline ${text} cannot be read yet: ${error.message}`
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
if (status === "done") return {
|
|
4206
|
+
met: true,
|
|
4207
|
+
detail: `mainline ${text} is done`
|
|
4208
|
+
};
|
|
4209
|
+
if (status === "dropped") return {
|
|
4210
|
+
met: false,
|
|
4211
|
+
unreachable: true,
|
|
4212
|
+
detail: `mainline ${text} was dropped, so it will never be done`
|
|
4213
|
+
};
|
|
4214
|
+
return {
|
|
4215
|
+
met: false,
|
|
4216
|
+
detail: `mainline ${text} is ${status}`
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
3920
4219
|
/** Check a wait condition once. */
|
|
3921
4220
|
function untilState(root, until) {
|
|
3922
4221
|
switch (until.kind) {
|
|
3923
4222
|
case "commit": return commitState(root, until.branch, until.text);
|
|
3924
4223
|
case "action-done": return actionState(root, `${until.session}#${until.action}`);
|
|
4224
|
+
case "mainline-done": return mainlineState(root, until.mainline);
|
|
3925
4225
|
case "file": return existsSync(resolve(root, until.path)) ? {
|
|
3926
4226
|
met: true,
|
|
3927
4227
|
detail: `${until.path} exists`
|
|
@@ -7939,4 +8239,4 @@ function openInApp(hub, target, closeAfterMs = 150) {
|
|
|
7939
8239
|
return true;
|
|
7940
8240
|
}
|
|
7941
8241
|
//#endregion
|
|
7942
|
-
export {
|
|
8242
|
+
export { nowMinute as $, mayDecorate as A, applyOperation as B, readSession as C, WriteError as Ct, decorateReply as D, matchGrants as E, renumberRequest as F, sectionText as G, HubError as H, renumberFiles as I, linksInto as J, dependencyGraph as K, renumberPath as L, traceTranscript as M, transcriptTitle as N, endsWithMarker as O, applyOffline as P, unavailableHubs as Q, renumberText as R, loadSessions as S, DATA_DIR as St, grantScope as T, splitFrontMatter as Tt, loadConfig as U, checkHubName as V, loadHub as W, refIndex as X, memberOfGroup as Y, remoteMembers as Z, initHub as _, requireSlug as _t, openInApp as a, MAX_FILE_BYTES as at, sessionBriefing as b, worktreesOf as bt, siteBlock as c, comparePaths as ct, retroReport as d, requestTime as dt, readYaml as et, retroReportFromReceiver as f, textBytes as ft, setArchivedMany as g, TASK_FILE as gt, ARCHIVE_KINDS as h, MAINLINE_FILE as ht, hostName as i, MAX_BLOB_BYTES as it, attributeTrace as j, headerLine as k, syncHost as l, exceedsFileLimit as lt, RETRY_RULES as m, hubFormatVersion as mt, DEFAULT_UI_PORT as n, ACCESS_HEADERS as nt, removeDanglingLinks as o, ROUTES as ot, receiverFromEnv as p, formatMismatch as pt, mainlineEdges as q, caddySites as r, BLOB_HASH as rt, removeHostLink as s, SOCKET_MAX_AGE_CLOSE as st, APP_BUNDLE_ID as t, nodeStorage as tt, migrateHub as u, readsAsText as ut, untilState as v, currentBranch as vt, transcriptPath as w, contentHash as wt, turnReminder as x, ConflictError as xt, duplicateClaimLines as y, findHubRoot as yt, scopeOfPath as z };
|
package/dist/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as openInApp, c as siteBlock, i as hostName, l as syncHost, n as DEFAULT_UI_PORT, o as removeDanglingLinks, r as caddySites, s as removeHostLink, t as APP_BUNDLE_ID } from "./host-
|
|
1
|
+
import { a as openInApp, c as siteBlock, i as hostName, l as syncHost, n as DEFAULT_UI_PORT, o as removeDanglingLinks, r as caddySites, s as removeHostLink, t as APP_BUNDLE_ID } from "./host-lY8ytTbw.js";
|
|
2
2
|
export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, removeDanglingLinks, removeHostLink, siteBlock, syncHost };
|
|
@@ -250,7 +250,8 @@ type Task = z.infer<typeof taskSchema>;
|
|
|
250
250
|
/**
|
|
251
251
|
* A condition a program can check, written `{kind}:{argument}`. `commit` holds once a commit message on the
|
|
252
252
|
* branch contains the text, `action-done` once the action is done, `file` once the path exists under the hub
|
|
253
|
-
* root
|
|
253
|
+
* root, `mainline-done` once the mainline (`m{NNN}` or `{hub}:m{NNN}`) is done. Git branch names cannot contain a
|
|
254
|
+
* colon, so the text after the branch may.
|
|
254
255
|
*/
|
|
255
256
|
type Until = {
|
|
256
257
|
kind: 'commit';
|
|
@@ -263,6 +264,9 @@ type Until = {
|
|
|
263
264
|
} | {
|
|
264
265
|
kind: 'file';
|
|
265
266
|
path: string;
|
|
267
|
+
} | {
|
|
268
|
+
kind: 'mainline-done';
|
|
269
|
+
mainline: string;
|
|
266
270
|
};
|
|
267
271
|
/**
|
|
268
272
|
* Why a blocked action cannot move, what it waits for, and the condition a program checks to end the wait.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ct as WriteError, St as DATA_DIR, i as hostName, l as syncHost, n as DEFAULT_UI_PORT, s as removeHostLink } from "./host-lY8ytTbw.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -276,7 +276,7 @@ async function launchUi(root, hub, configured, options) {
|
|
|
276
276
|
}
|
|
277
277
|
let server;
|
|
278
278
|
if (!reuse) {
|
|
279
|
-
const { serveUi } = await import("./ui-
|
|
279
|
+
const { serveUi } = await import("./ui-AhBsqtZe.js");
|
|
280
280
|
server = await serveUi(root, choice.port);
|
|
281
281
|
writeUiRecord(root, {
|
|
282
282
|
port: choice.port,
|
package/dist/launch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as networkProbe, c as realRoot, d as withUiLock, i as launchUi, l as startUiInBackground, n as choosePort, o as portCandidates, r as exitCodeFor, s as readUiRecord, t as PORT_SPAN, u as watchHubGone } from "./launch-
|
|
1
|
+
import { a as networkProbe, c as realRoot, d as withUiLock, i as launchUi, l as startUiInBackground, n as choosePort, o as portCandidates, r as exitCodeFor, s as readUiRecord, t as PORT_SPAN, u as watchHubGone } from "./launch-3HLHjQF0.js";
|
|
2
2
|
export { PORT_SPAN, choosePort, exitCodeFor, launchUi, networkProbe, portCandidates, readUiRecord, realRoot, startUiInBackground, watchHubGone, withUiLock };
|
|
Binary file
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as nowMinute, B as applyOperation, Ct as WriteError, F as renumberRequest, I as renumberFiles, L as renumberPath, P as applyOffline, R as renumberText, St as DATA_DIR, Tt as splitFrontMatter, at as MAX_FILE_BYTES, ct as comparePaths, dt as requestTime, ft as textBytes, it as MAX_BLOB_BYTES, lt as exceedsFileLimit, nt as ACCESS_HEADERS, ot as ROUTES, rt as BLOB_HASH, st as SOCKET_MAX_AGE_CLOSE, tt as nodeStorage, ut as readsAsText, wt as contentHash, xt as ConflictError, z as scopeOfPath } from "./host-lY8ytTbw.js";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { F as configSchema, f as GROUP_SECTIONS } from "./dist-
|
|
2
|
-
import { C as readSession, G as sectionText, H as HubError, J as linksInto, K as dependencyGraph, M as traceTranscript, N as transcriptTitle, S as loadSessions, U as loadConfig, W as loadHub,
|
|
1
|
+
import { F as configSchema, f as GROUP_SECTIONS } from "./dist-ud09dzZo.js";
|
|
2
|
+
import { C as readSession, Ct as WriteError, G as sectionText, H as HubError, J as linksInto, K as dependencyGraph, M as traceTranscript, N as transcriptTitle, S as loadSessions, St as DATA_DIR, U as loadConfig, W as loadHub, X as refIndex, Z as remoteMembers, a as openInApp, et as readYaml, g as setArchivedMany, h as ARCHIVE_KINDS, j as attributeTrace, q as mainlineEdges, w as transcriptPath, xt as ConflictError } from "./host-lY8ytTbw.js";
|
|
3
3
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
4
|
-
import { A as isSynced, _ as readDaemon, a as remoteMode, b as readRemote, h as callDaemon, j as syncPaths, k as SYNC_DIR, m as DaemonUnavailable, n as hubWriter, r as writtenLines, y as watchTree } from "./operation-
|
|
5
|
-
import { c as realRoot, f as CLI_VERSION, s as readUiRecord } from "./launch-
|
|
4
|
+
import { A as isSynced, _ as readDaemon, a as remoteMode, b as readRemote, h as callDaemon, j as syncPaths, k as SYNC_DIR, m as DaemonUnavailable, n as hubWriter, r as writtenLines, y as watchTree } from "./operation-BZ7CHB6J.js";
|
|
5
|
+
import { c as realRoot, f as CLI_VERSION, s as readUiRecord } from "./launch-3HLHjQF0.js";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { closeSync, existsSync, lstatSync, openSync, readFileSync, readSync, realpathSync, statSync } from "node:fs";
|
|
8
8
|
import { basename, dirname, extname, join, normalize, relative, resolve, sep } from "node:path";
|
|
@@ -23,7 +23,7 @@ const WRITE_HEADER = "x-mainflow-write";
|
|
|
23
23
|
/** Every reference with a hub prefix in the documents of `hub`: links, origins and replaced items. */
|
|
24
24
|
function prefixedRefs(hub) {
|
|
25
25
|
const refs = [];
|
|
26
|
-
const doc = (d) => refs.push(d.origin, ...(d.links ?? []).map((l) => l.to));
|
|
26
|
+
const doc = (d) => refs.push(d.origin, d.group, ...(d.links ?? []).map((l) => l.to));
|
|
27
27
|
for (const m of hub.mainlines) {
|
|
28
28
|
doc(m);
|
|
29
29
|
refs.push(...m.criteria.map((c) => c.supersededBy));
|
|
@@ -46,7 +46,13 @@ function relationsView(hub) {
|
|
|
46
46
|
const targets = {};
|
|
47
47
|
const graph = dependencyGraph(hub);
|
|
48
48
|
const depRefs = graph.edges.flatMap((e) => [e.from, e.to]);
|
|
49
|
-
|
|
49
|
+
const groupMembers = remoteMembers(hub.remotes, hub.config.hub);
|
|
50
|
+
const memberRefs = [...groupMembers.values()].flatMap((list) => list.map((m) => m.ref));
|
|
51
|
+
for (const ref of /* @__PURE__ */ new Set([
|
|
52
|
+
...prefixedRefs(hub),
|
|
53
|
+
...depRefs.filter((r) => r.includes(":")),
|
|
54
|
+
...memberRefs
|
|
55
|
+
])) {
|
|
50
56
|
const colon = ref.indexOf(":");
|
|
51
57
|
const name = ref.slice(0, colon);
|
|
52
58
|
const found = name === hub.config.hub ? own.get(ref.slice(colon + 1)) : indexes.get(name)?.get(ref.slice(colon + 1));
|
|
@@ -77,6 +83,7 @@ function relationsView(hub) {
|
|
|
77
83
|
targets,
|
|
78
84
|
backlinks,
|
|
79
85
|
deps: graph.edges,
|
|
86
|
+
groupMembers: Object.fromEntries(groupMembers),
|
|
80
87
|
mainlineDeps: mainlineEdges(graph),
|
|
81
88
|
cycles: graph.cycles
|
|
82
89
|
};
|