@rness/cli 0.12.0 → 0.13.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/README.md +140 -22
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-AEW7ZDFS.js → chunk-4VTYSPZW.js} +1070 -217
- package/dist/chunk-4VTYSPZW.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-AEW7ZDFS.js.map +0 -1
|
@@ -139,6 +139,36 @@ async function changedPaths(dir, paths) {
|
|
|
139
139
|
}
|
|
140
140
|
return changed;
|
|
141
141
|
}
|
|
142
|
+
async function seenPaths(dir, paths) {
|
|
143
|
+
const seen = /* @__PURE__ */ new Set();
|
|
144
|
+
for (const path of new Set(paths)) {
|
|
145
|
+
try {
|
|
146
|
+
const commit = await git(
|
|
147
|
+
[
|
|
148
|
+
"log",
|
|
149
|
+
"-1",
|
|
150
|
+
"--full-history",
|
|
151
|
+
"--format=%H",
|
|
152
|
+
"HEAD",
|
|
153
|
+
"--",
|
|
154
|
+
`:(literal)${path}`
|
|
155
|
+
],
|
|
156
|
+
dir
|
|
157
|
+
);
|
|
158
|
+
if (commit.trim() !== "") seen.add(path);
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return seen;
|
|
163
|
+
}
|
|
164
|
+
async function gitPath(dir, name) {
|
|
165
|
+
try {
|
|
166
|
+
const path = (await git(["rev-parse", "--git-path", name], dir)).trim();
|
|
167
|
+
return path === "" ? null : resolve(dir, path);
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
142
172
|
async function pullFastForward(dir, credentials) {
|
|
143
173
|
await git(["pull", "--ff-only", "--quiet"], dir, credentials);
|
|
144
174
|
}
|
|
@@ -360,9 +390,9 @@ function readScopes(value) {
|
|
|
360
390
|
scopes[name] = { path, extends: ext };
|
|
361
391
|
}
|
|
362
392
|
for (const [name, entry] of Object.entries(scopes)) {
|
|
363
|
-
for (const
|
|
364
|
-
if (!Object.hasOwn(scopes,
|
|
365
|
-
fail(`scope "${name}" extends unknown scope "${
|
|
393
|
+
for (const target2 of entry.extends) {
|
|
394
|
+
if (!Object.hasOwn(scopes, target2))
|
|
395
|
+
fail(`scope "${name}" extends unknown scope "${target2}"`);
|
|
366
396
|
}
|
|
367
397
|
}
|
|
368
398
|
return scopes;
|
|
@@ -456,7 +486,7 @@ function parseRepoSpec(spec, org, host) {
|
|
|
456
486
|
}
|
|
457
487
|
|
|
458
488
|
// src/version.ts
|
|
459
|
-
var VERSION = "0.
|
|
489
|
+
var VERSION = "0.13.0";
|
|
460
490
|
|
|
461
491
|
// src/core/pm.ts
|
|
462
492
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -599,8 +629,8 @@ async function collectMarkdown(dir) {
|
|
|
599
629
|
}
|
|
600
630
|
|
|
601
631
|
// src/core/scope.ts
|
|
602
|
-
function isPrefix(prefix,
|
|
603
|
-
return prefix ===
|
|
632
|
+
function isPrefix(prefix, target2) {
|
|
633
|
+
return prefix === target2 || target2.startsWith(`${prefix}/`);
|
|
604
634
|
}
|
|
605
635
|
function resolveScope(manifest, relDir) {
|
|
606
636
|
let best = null;
|
|
@@ -624,7 +654,7 @@ function scopeChain(manifest, scope) {
|
|
|
624
654
|
chain.push(name);
|
|
625
655
|
stack.add(name);
|
|
626
656
|
const entry = Object.hasOwn(manifest.scopes, name) ? manifest.scopes[name] : void 0;
|
|
627
|
-
for (const
|
|
657
|
+
for (const target2 of entry?.extends ?? []) visit(target2, stack);
|
|
628
658
|
stack.delete(name);
|
|
629
659
|
};
|
|
630
660
|
visit(scope, /* @__PURE__ */ new Set());
|
|
@@ -1116,6 +1146,158 @@ function statusMarkdown(workspace, tabs) {
|
|
|
1116
1146
|
return out.join("\n");
|
|
1117
1147
|
}
|
|
1118
1148
|
|
|
1149
|
+
// src/pulse/body.ts
|
|
1150
|
+
import { createHash } from "crypto";
|
|
1151
|
+
import { posix as posix4 } from "path";
|
|
1152
|
+
var BODY_LIMIT = 65536;
|
|
1153
|
+
var blobUrl = (org, path) => `https://github.com/${org}/.rness/blob/main/${encodeURI(path)}`;
|
|
1154
|
+
var issueUrl = (org, number) => `https://github.com/${org}/.rness/issues/${number}`;
|
|
1155
|
+
function headerOf(path, org) {
|
|
1156
|
+
return `\`${path}\` \xB7 [on GitHub](${blobUrl(org, path)})`;
|
|
1157
|
+
}
|
|
1158
|
+
var digest = (text) => createHash("sha256").update(text, "utf8").digest("hex").slice(0, 12);
|
|
1159
|
+
var comment = (d) => `<!-- rness ${d} -->`;
|
|
1160
|
+
var TAIL = `
|
|
1161
|
+
|
|
1162
|
+
${comment("0".repeat(12))}`.length;
|
|
1163
|
+
var LAST_LINE = /\n\n<!-- rness ([0-9a-f]{12}) -->\s*$/;
|
|
1164
|
+
function digestOf(body) {
|
|
1165
|
+
const text = body.replace(/\r\n?/g, "\n");
|
|
1166
|
+
const m = LAST_LINE.exec(text);
|
|
1167
|
+
const d = m?.[1];
|
|
1168
|
+
if (m === null || d === void 0) return null;
|
|
1169
|
+
return digest(text.slice(0, m.index)) === d ? d : null;
|
|
1170
|
+
}
|
|
1171
|
+
var OPENER = /^[ \t]*(`{3,}|~{3,})(.*)$/;
|
|
1172
|
+
var CLOSER = /^[ \t]*(`{3,}|~{3,})[ \t]*$/;
|
|
1173
|
+
function fenced(lines) {
|
|
1174
|
+
const code = [];
|
|
1175
|
+
let run3 = null;
|
|
1176
|
+
for (const line3 of lines) {
|
|
1177
|
+
if (run3 === null) {
|
|
1178
|
+
const m = OPENER.exec(line3);
|
|
1179
|
+
const fence2 = m?.[1];
|
|
1180
|
+
if (fence2 !== void 0 && !(fence2.startsWith("`") && (m?.[2] ?? "").includes("`")))
|
|
1181
|
+
run3 = fence2;
|
|
1182
|
+
code.push(run3 !== null);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
code.push(true);
|
|
1186
|
+
const fence = CLOSER.exec(line3)?.[1];
|
|
1187
|
+
if (fence !== void 0 && fence[0] === run3[0] && fence.length >= run3.length)
|
|
1188
|
+
run3 = null;
|
|
1189
|
+
}
|
|
1190
|
+
return code;
|
|
1191
|
+
}
|
|
1192
|
+
var TITLE = /^ {0,3}#(?:[ \t]|$)/;
|
|
1193
|
+
function withoutTitle(lines) {
|
|
1194
|
+
const code = fenced(lines);
|
|
1195
|
+
const at = lines.findIndex((line3, i) => code[i] !== true && TITLE.test(line3));
|
|
1196
|
+
if (at < 0) return [...lines];
|
|
1197
|
+
let end = at + 1;
|
|
1198
|
+
while (end < lines.length && (lines[end] ?? "").trim() === "") end++;
|
|
1199
|
+
return [...lines.slice(0, at), ...lines.slice(end)];
|
|
1200
|
+
}
|
|
1201
|
+
var SCHEME = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
1202
|
+
var IMAGE = /\.(?:png|jpe?g|gif|svg|webp|avif)$/i;
|
|
1203
|
+
var decoded = (path) => {
|
|
1204
|
+
try {
|
|
1205
|
+
return decodeURI(path);
|
|
1206
|
+
} catch {
|
|
1207
|
+
return path;
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
function target(dest, s) {
|
|
1211
|
+
const raw = dest.startsWith("<") ? dest.slice(1, -1) : dest;
|
|
1212
|
+
if (raw === "" || SCHEME.test(raw) || raw.startsWith("//") || raw.startsWith("#"))
|
|
1213
|
+
return dest;
|
|
1214
|
+
const hash = raw.indexOf("#");
|
|
1215
|
+
const pathPart = hash < 0 ? raw : raw.slice(0, hash);
|
|
1216
|
+
const anchor = hash < 0 ? "" : raw.slice(hash);
|
|
1217
|
+
const file = decoded(
|
|
1218
|
+
posix4.normalize(
|
|
1219
|
+
pathPart.startsWith("/") ? pathPart.slice(1) : posix4.join(posix4.dirname(s.path), pathPart)
|
|
1220
|
+
)
|
|
1221
|
+
);
|
|
1222
|
+
const number = s.numbers.get(file);
|
|
1223
|
+
if (number !== void 0) return issueUrl(s.org, number);
|
|
1224
|
+
if (!s.files.has(file)) return dest;
|
|
1225
|
+
return `${blobUrl(s.org, file)}${IMAGE.test(file) ? "?raw=true" : ""}${anchor}`;
|
|
1226
|
+
}
|
|
1227
|
+
var PROTECTED = new RegExp(
|
|
1228
|
+
[
|
|
1229
|
+
/(?<!`)(`+)(?!`)[\s\S]*?(?<!`)\1(?!`)/.source,
|
|
1230
|
+
/<!--[\s\S]*?-->/.source,
|
|
1231
|
+
/<[a-zA-Z][a-zA-Z0-9+.-]*:[^\s<>]*>/.source,
|
|
1232
|
+
/^ {0,3}\[(?:[^\]\\\n]|\\.)+\]:[ \t]*(?<def><[^>\n]*>|\S+)/.source,
|
|
1233
|
+
/\]\([ \t]*(?<dest><[^>\n]*>|[^\s()]+)/.source,
|
|
1234
|
+
/\b(?:https?:\/\/|www\.)[^\s<>]*[^\s<>.,:;"')\]]/.source
|
|
1235
|
+
].join("|"),
|
|
1236
|
+
"gm"
|
|
1237
|
+
);
|
|
1238
|
+
var MENTION = /(^|[^A-Za-z0-9`\\])@([A-Za-z0-9][A-Za-z0-9-]*)/g;
|
|
1239
|
+
var REFERENCE = /(^|[^A-Za-z0-9&#/])#(\d+)(?![A-Za-z0-9])/g;
|
|
1240
|
+
var ZWSP = "\u200B";
|
|
1241
|
+
var neutral = (text) => text.replace(MENTION, "$1\\@$2").replace(REFERENCE, `$1#${ZWSP}$2`);
|
|
1242
|
+
function inline(text, s) {
|
|
1243
|
+
let out = "";
|
|
1244
|
+
let from = 0;
|
|
1245
|
+
for (const m of text.matchAll(PROTECTED)) {
|
|
1246
|
+
const at = m.index ?? 0;
|
|
1247
|
+
out += neutral(text.slice(from, at));
|
|
1248
|
+
const dest = m.groups?.["dest"] ?? m.groups?.["def"];
|
|
1249
|
+
out += dest === void 0 ? m[0] : `${m[0].slice(0, m[0].length - dest.length)}${target(dest, s)}`;
|
|
1250
|
+
from = at + m[0].length;
|
|
1251
|
+
}
|
|
1252
|
+
return out + neutral(text.slice(from));
|
|
1253
|
+
}
|
|
1254
|
+
function rewrite(lines, s) {
|
|
1255
|
+
const code = fenced(lines);
|
|
1256
|
+
const out = [];
|
|
1257
|
+
let paragraph = [];
|
|
1258
|
+
const flush = () => {
|
|
1259
|
+
if (paragraph.length > 0) out.push(inline(paragraph.join("\n"), s));
|
|
1260
|
+
paragraph = [];
|
|
1261
|
+
};
|
|
1262
|
+
lines.forEach((line3, i) => {
|
|
1263
|
+
if (code[i] === true || line3.trim() === "") {
|
|
1264
|
+
flush();
|
|
1265
|
+
out.push(line3);
|
|
1266
|
+
} else paragraph.push(line3);
|
|
1267
|
+
});
|
|
1268
|
+
flush();
|
|
1269
|
+
return out;
|
|
1270
|
+
}
|
|
1271
|
+
function cut(text, rest) {
|
|
1272
|
+
const room = BODY_LIMIT - TAIL - 2 - rest.length;
|
|
1273
|
+
const lines = text.split("\n");
|
|
1274
|
+
const code = fenced(lines);
|
|
1275
|
+
let length = -1;
|
|
1276
|
+
let at = 1;
|
|
1277
|
+
for (let k = 1; k < lines.length; k++) {
|
|
1278
|
+
length += 1 + (lines[k - 1] ?? "").length;
|
|
1279
|
+
if (length > room) break;
|
|
1280
|
+
if (code[k] !== true && (lines[k] ?? "").trim() === "") at = k;
|
|
1281
|
+
}
|
|
1282
|
+
return `${lines.slice(0, at).join("\n").trimEnd()}
|
|
1283
|
+
|
|
1284
|
+
${rest}`;
|
|
1285
|
+
}
|
|
1286
|
+
function issueBody(s) {
|
|
1287
|
+
const lines = withoutTitle(
|
|
1288
|
+
stripFrontMatter(s.text.replace(/\r\n?/g, "\n")).split("\n")
|
|
1289
|
+
);
|
|
1290
|
+
const content = rewrite(lines, s).join("\n").replace(/^(?:[ \t]*\n)+/, "").trimEnd();
|
|
1291
|
+
const header2 = headerOf(s.path, s.org);
|
|
1292
|
+
const whole = content === "" ? header2 : `${header2}
|
|
1293
|
+
|
|
1294
|
+
${content}`;
|
|
1295
|
+
const text = whole.length + TAIL <= BODY_LIMIT ? whole : cut(whole, `The rest: ${blobUrl(s.org, s.path)}`);
|
|
1296
|
+
return `${text}
|
|
1297
|
+
|
|
1298
|
+
${comment(digest(text))}`;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1119
1301
|
// src/pulse/layout.ts
|
|
1120
1302
|
var statusOf2 = (status2) => status2 === null || status2 === "?" ? null : status2;
|
|
1121
1303
|
var LIFECYCLE = [
|
|
@@ -1142,8 +1324,8 @@ var STATUS_COLORS = {
|
|
|
1142
1324
|
Accepted: "green",
|
|
1143
1325
|
Implemented: "green",
|
|
1144
1326
|
Completed: "green",
|
|
1145
|
-
Rejected: "
|
|
1146
|
-
Superseded: "
|
|
1327
|
+
Rejected: "red",
|
|
1328
|
+
Superseded: "yellow",
|
|
1147
1329
|
Abandoned: "gray"
|
|
1148
1330
|
};
|
|
1149
1331
|
var COLLECTION_COLORS = {
|
|
@@ -1208,9 +1390,7 @@ function desiredOf(tabs, org) {
|
|
|
1208
1390
|
(tab) => tab.rows.map((row) => ({
|
|
1209
1391
|
path: row.path,
|
|
1210
1392
|
title: `${row.id} \u2014 ${row.title}`,
|
|
1211
|
-
body:
|
|
1212
|
-
|
|
1213
|
-
https://github.com/${org}/.rness/blob/main/${row.path}`,
|
|
1393
|
+
body: headerOf(row.path, org),
|
|
1214
1394
|
status: statusOf2(row.status),
|
|
1215
1395
|
type: tab.label,
|
|
1216
1396
|
statusField: fields2.get(tab.label)?.name ?? null
|
|
@@ -1219,6 +1399,7 @@ https://github.com/${org}/.rness/blob/main/${row.path}`,
|
|
|
1219
1399
|
}
|
|
1220
1400
|
|
|
1221
1401
|
// src/core/github.ts
|
|
1402
|
+
import { setTimeout as delay } from "timers/promises";
|
|
1222
1403
|
var DEFAULT_GITHUB_API = "https://api.github.com";
|
|
1223
1404
|
var MAX_PAGES = 50;
|
|
1224
1405
|
var PER_PAGE = 100;
|
|
@@ -1353,6 +1534,39 @@ async function listRepositories(owner, options) {
|
|
|
1353
1534
|
truncated: true
|
|
1354
1535
|
};
|
|
1355
1536
|
}
|
|
1537
|
+
var WAIT_BUDGET_MS = 6e5;
|
|
1538
|
+
var DEFAULT_WAIT_MS = 6e4;
|
|
1539
|
+
var MIN_WAIT_MS = 1e3;
|
|
1540
|
+
var RATE_LIMIT_OUTLASTED = "GitHub's rate limit outlasted the 10 minutes rness waits";
|
|
1541
|
+
function rateWait(sleep = (ms) => delay(ms)) {
|
|
1542
|
+
return { left: WAIT_BUDGET_MS, now: () => Date.now(), sleep };
|
|
1543
|
+
}
|
|
1544
|
+
var PROCESS_WAIT = rateWait();
|
|
1545
|
+
var RateLimitError = class extends Error {
|
|
1546
|
+
};
|
|
1547
|
+
var messageOf = (body) => {
|
|
1548
|
+
const m = body !== null && typeof body === "object" ? body["message"] : void 0;
|
|
1549
|
+
return typeof m === "string" ? m : "";
|
|
1550
|
+
};
|
|
1551
|
+
function rateDelay(page, now, limited = false) {
|
|
1552
|
+
const h = page.headers;
|
|
1553
|
+
const spent = h.get("x-ratelimit-remaining") === "0";
|
|
1554
|
+
const hit = limited || page.status === 429 || page.status === 403 && (h.has("retry-after") || spent || /rate limit/i.test(messageOf(page.body)));
|
|
1555
|
+
if (!hit) return null;
|
|
1556
|
+
const after = (h.get("retry-after") ?? "").trim();
|
|
1557
|
+
if (/^\d+$/.test(after)) return Number(after) * 1e3;
|
|
1558
|
+
const reset = (h.get("x-ratelimit-reset") ?? "").trim();
|
|
1559
|
+
if (spent && /^\d+$/.test(reset))
|
|
1560
|
+
return Math.max(0, Number(reset) * 1e3 - now);
|
|
1561
|
+
return DEFAULT_WAIT_MS;
|
|
1562
|
+
}
|
|
1563
|
+
async function waitOut(options, ms) {
|
|
1564
|
+
const wait = options.wait ?? PROCESS_WAIT;
|
|
1565
|
+
const spent = Math.max(ms, MIN_WAIT_MS);
|
|
1566
|
+
if (spent > wait.left) throw new RateLimitError(RATE_LIMIT_OUTLASTED);
|
|
1567
|
+
wait.left -= spent;
|
|
1568
|
+
await wait.sleep(spent);
|
|
1569
|
+
}
|
|
1356
1570
|
function apiHeaders(token) {
|
|
1357
1571
|
return {
|
|
1358
1572
|
Accept: "application/vnd.github+json",
|
|
@@ -1375,23 +1589,34 @@ async function getJson(path, options) {
|
|
|
1375
1589
|
const body = await res.json().catch(() => void 0);
|
|
1376
1590
|
return { status: res.status, body };
|
|
1377
1591
|
}
|
|
1378
|
-
async function postJson(path, payload, options, explain = false) {
|
|
1592
|
+
async function postJson(path, payload, options, explain = false, limited = () => false) {
|
|
1379
1593
|
const base = (options.apiBase ?? DEFAULT_GITHUB_API).replace(/\/+$/, "");
|
|
1380
|
-
const
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1594
|
+
const clock = options.wait ?? PROCESS_WAIT;
|
|
1595
|
+
for (; ; ) {
|
|
1596
|
+
const page = await getPage(
|
|
1597
|
+
`${base}${path}`,
|
|
1598
|
+
{ ...apiHeaders(options.token), "Content-Type": "application/json" },
|
|
1599
|
+
options.timeoutMs ?? 15e3,
|
|
1600
|
+
// A refusal's body says whether it is a rate limit. A timeout throws
|
|
1601
|
+
// above and is never retried: the write may have been made.
|
|
1602
|
+
{ method: "POST", body: JSON.stringify(payload), errorBody: true }
|
|
1603
|
+
);
|
|
1604
|
+
const ok = page.status >= 200 && page.status < 300;
|
|
1605
|
+
const wait = rateDelay(page, clock.now(), ok && limited(page.body));
|
|
1606
|
+
if (wait !== null) {
|
|
1607
|
+
await waitOut(options, wait);
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (!ok) {
|
|
1611
|
+
const message2 = explain && page.status !== 401 && page.status !== 429 && page.body !== null && typeof page.body === "object" ? page.body.message : void 0;
|
|
1612
|
+
if (typeof message2 === "string" && message2 !== "")
|
|
1613
|
+
throw new GitHubMessageError(message2, {
|
|
1614
|
+
cause: { status: page.status, path }
|
|
1615
|
+
});
|
|
1616
|
+
throw httpError(page, path);
|
|
1617
|
+
}
|
|
1618
|
+
return page.body;
|
|
1393
1619
|
}
|
|
1394
|
-
return page.body;
|
|
1395
1620
|
}
|
|
1396
1621
|
async function getScopes(options) {
|
|
1397
1622
|
const base = (options.apiBase ?? DEFAULT_GITHUB_API).replace(/\/+$/, "");
|
|
@@ -1489,8 +1714,17 @@ async function createRepository(owner, name, options) {
|
|
|
1489
1714
|
function isRecord2(v) {
|
|
1490
1715
|
return v !== null && typeof v === "object";
|
|
1491
1716
|
}
|
|
1717
|
+
var rateLimited = (body) => isRecord2(body) && Array.isArray(body["errors"]) && body["errors"].some(
|
|
1718
|
+
(e) => isRecord2(e) && e["type"] === "RATE_LIMITED"
|
|
1719
|
+
);
|
|
1492
1720
|
async function graphqlRaw(query, variables, options) {
|
|
1493
|
-
const body = await postJson(
|
|
1721
|
+
const body = await postJson(
|
|
1722
|
+
"/graphql",
|
|
1723
|
+
{ query, variables },
|
|
1724
|
+
options,
|
|
1725
|
+
true,
|
|
1726
|
+
rateLimited
|
|
1727
|
+
);
|
|
1494
1728
|
return isRecord2(body) ? body : {};
|
|
1495
1729
|
}
|
|
1496
1730
|
function firstError(answer) {
|
|
@@ -1505,6 +1739,15 @@ async function graphql(query, variables, options) {
|
|
|
1505
1739
|
throw new Error("GitHub GraphQL answered without data");
|
|
1506
1740
|
return answer.data;
|
|
1507
1741
|
}
|
|
1742
|
+
async function graphqlFound(query, variables, options) {
|
|
1743
|
+
const answer = await graphqlRaw(query, variables, options);
|
|
1744
|
+
const errors = answer.errors ?? [];
|
|
1745
|
+
if (errors.length > 0 && errors.every((e) => e.type === "NOT_FOUND"))
|
|
1746
|
+
return null;
|
|
1747
|
+
const error = firstError(answer);
|
|
1748
|
+
if (error !== null) throw error;
|
|
1749
|
+
return answer.data ?? null;
|
|
1750
|
+
}
|
|
1508
1751
|
async function orgId(org, o) {
|
|
1509
1752
|
const d = await graphql(
|
|
1510
1753
|
"query($login: String!) { organization(login: $login) { id } }",
|
|
@@ -1541,7 +1784,9 @@ async function findProject(org, number, o) {
|
|
|
1541
1784
|
{ login: org, number },
|
|
1542
1785
|
o
|
|
1543
1786
|
);
|
|
1544
|
-
|
|
1787
|
+
const errors = answer.errors ?? [];
|
|
1788
|
+
if (errors.length > 0 && errors.every((e) => e.type === "NOT_FOUND"))
|
|
1789
|
+
return null;
|
|
1545
1790
|
const error = firstError(answer);
|
|
1546
1791
|
if (error !== null) throw error;
|
|
1547
1792
|
return answer.data?.organization?.projectV2 ?? null;
|
|
@@ -1621,7 +1866,7 @@ async function setOptions(field, options, o) {
|
|
|
1621
1866
|
);
|
|
1622
1867
|
return toField(d.updateProjectV2Field.projectV2Field);
|
|
1623
1868
|
}
|
|
1624
|
-
async function listItems(projectId, o) {
|
|
1869
|
+
async function listItems(projectId, o, { bodies = true } = {}) {
|
|
1625
1870
|
const items = [];
|
|
1626
1871
|
let cursor = null;
|
|
1627
1872
|
for (; ; ) {
|
|
@@ -1639,8 +1884,23 @@ async function listItems(projectId, o) {
|
|
|
1639
1884
|
id
|
|
1640
1885
|
title
|
|
1641
1886
|
}
|
|
1887
|
+
... on Issue {
|
|
1888
|
+
id
|
|
1889
|
+
number
|
|
1890
|
+
title
|
|
1891
|
+
state
|
|
1892
|
+
${bodies ? "body" : ""}
|
|
1893
|
+
repository {
|
|
1894
|
+
nameWithOwner
|
|
1895
|
+
}
|
|
1896
|
+
labels(first: 20) {
|
|
1897
|
+
nodes {
|
|
1898
|
+
name
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1642
1902
|
}
|
|
1643
|
-
fieldValues(first:
|
|
1903
|
+
fieldValues(first: 50) {
|
|
1644
1904
|
nodes {
|
|
1645
1905
|
... on ProjectV2ItemFieldTextValue {
|
|
1646
1906
|
text
|
|
@@ -1680,10 +1940,20 @@ async function listItems(projectId, o) {
|
|
|
1680
1940
|
const value = v.text ?? v.name;
|
|
1681
1941
|
if (field !== void 0 && value !== void 0) values2[field] = value;
|
|
1682
1942
|
}
|
|
1943
|
+
const c = n.content;
|
|
1944
|
+
const issue = c?.id !== void 0 && c.number !== void 0 ? {
|
|
1945
|
+
id: c.id,
|
|
1946
|
+
number: c.number,
|
|
1947
|
+
open: c.state === "OPEN",
|
|
1948
|
+
repository: c.repository?.nameWithOwner ?? "",
|
|
1949
|
+
body: bodies ? c.body ?? "" : null,
|
|
1950
|
+
labels: (c.labels?.nodes ?? []).map((l) => l.name ?? "").filter((l) => l !== "")
|
|
1951
|
+
} : null;
|
|
1683
1952
|
items.push({
|
|
1684
1953
|
id: n.id,
|
|
1685
|
-
draftId:
|
|
1686
|
-
|
|
1954
|
+
draftId: issue === null ? c?.id ?? null : null,
|
|
1955
|
+
issue,
|
|
1956
|
+
title: c?.title ?? "",
|
|
1687
1957
|
archived: n.isArchived === true,
|
|
1688
1958
|
values: values2
|
|
1689
1959
|
});
|
|
@@ -1693,41 +1963,6 @@ async function listItems(projectId, o) {
|
|
|
1693
1963
|
cursor = endCursor;
|
|
1694
1964
|
}
|
|
1695
1965
|
}
|
|
1696
|
-
async function addDraft(projectId, title, body, o) {
|
|
1697
|
-
const d = await graphql(
|
|
1698
|
-
`
|
|
1699
|
-
mutation ($projectId: ID!, $title: String!, $body: String) {
|
|
1700
|
-
addProjectV2DraftIssue(
|
|
1701
|
-
input: { projectId: $projectId, title: $title, body: $body }
|
|
1702
|
-
) {
|
|
1703
|
-
projectItem {
|
|
1704
|
-
id
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
`,
|
|
1709
|
-
{ projectId, title, body },
|
|
1710
|
-
o
|
|
1711
|
-
);
|
|
1712
|
-
return d.addProjectV2DraftIssue.projectItem.id;
|
|
1713
|
-
}
|
|
1714
|
-
async function editDraft(draftId, title, body, o) {
|
|
1715
|
-
await graphql(
|
|
1716
|
-
`
|
|
1717
|
-
mutation ($draftIssueId: ID!, $title: String!, $body: String) {
|
|
1718
|
-
updateProjectV2DraftIssue(
|
|
1719
|
-
input: { draftIssueId: $draftIssueId, title: $title, body: $body }
|
|
1720
|
-
) {
|
|
1721
|
-
draftIssue {
|
|
1722
|
-
id
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
`,
|
|
1727
|
-
{ draftIssueId: draftId, title, body },
|
|
1728
|
-
o
|
|
1729
|
-
);
|
|
1730
|
-
}
|
|
1731
1966
|
async function setValue(projectId, itemId, fieldId, value, o) {
|
|
1732
1967
|
if (value === null) {
|
|
1733
1968
|
await graphql(
|
|
@@ -1795,6 +2030,90 @@ async function archive(projectId, itemId, o) {
|
|
|
1795
2030
|
o
|
|
1796
2031
|
);
|
|
1797
2032
|
}
|
|
2033
|
+
async function addItem(projectId, contentId, o) {
|
|
2034
|
+
const d = await graphql(
|
|
2035
|
+
`
|
|
2036
|
+
mutation ($projectId: ID!, $contentId: ID!) {
|
|
2037
|
+
addProjectV2ItemById(
|
|
2038
|
+
input: { projectId: $projectId, contentId: $contentId }
|
|
2039
|
+
) {
|
|
2040
|
+
item {
|
|
2041
|
+
id
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
`,
|
|
2046
|
+
{ projectId, contentId },
|
|
2047
|
+
o
|
|
2048
|
+
);
|
|
2049
|
+
return d.addProjectV2ItemById.item.id;
|
|
2050
|
+
}
|
|
2051
|
+
async function convertDraft(itemId, repositoryId, o) {
|
|
2052
|
+
const d = await graphql(
|
|
2053
|
+
`
|
|
2054
|
+
mutation ($itemId: ID!, $repositoryId: ID!) {
|
|
2055
|
+
convertProjectV2DraftIssueItemToIssue(
|
|
2056
|
+
input: { itemId: $itemId, repositoryId: $repositoryId }
|
|
2057
|
+
) {
|
|
2058
|
+
item {
|
|
2059
|
+
id
|
|
2060
|
+
content {
|
|
2061
|
+
... on Issue {
|
|
2062
|
+
id
|
|
2063
|
+
number
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
`,
|
|
2070
|
+
{ itemId, repositoryId },
|
|
2071
|
+
o
|
|
2072
|
+
);
|
|
2073
|
+
const content = d.convertProjectV2DraftIssueItemToIssue.item.content;
|
|
2074
|
+
const id = content?.id;
|
|
2075
|
+
const number = content?.number;
|
|
2076
|
+
if (id === void 0 || number === void 0)
|
|
2077
|
+
throw new Error(`GitHub converted the item ${itemId} without an issue`);
|
|
2078
|
+
return { id, number };
|
|
2079
|
+
}
|
|
2080
|
+
async function linkedRepositories(projectId, o) {
|
|
2081
|
+
const d = await graphql(
|
|
2082
|
+
`
|
|
2083
|
+
query ($projectId: ID!) {
|
|
2084
|
+
node(id: $projectId) {
|
|
2085
|
+
... on ProjectV2 {
|
|
2086
|
+
repositories(first: 100) {
|
|
2087
|
+
nodes {
|
|
2088
|
+
nameWithOwner
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
`,
|
|
2095
|
+
{ projectId },
|
|
2096
|
+
o
|
|
2097
|
+
);
|
|
2098
|
+
return (d.node?.repositories.nodes ?? []).map((r) => r.nameWithOwner);
|
|
2099
|
+
}
|
|
2100
|
+
async function linkRepository(projectId, repositoryId, o) {
|
|
2101
|
+
await graphql(
|
|
2102
|
+
`
|
|
2103
|
+
mutation ($projectId: ID!, $repositoryId: ID!) {
|
|
2104
|
+
linkProjectV2ToRepository(
|
|
2105
|
+
input: { projectId: $projectId, repositoryId: $repositoryId }
|
|
2106
|
+
) {
|
|
2107
|
+
repository {
|
|
2108
|
+
id
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
`,
|
|
2113
|
+
{ projectId, repositoryId },
|
|
2114
|
+
o
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
1798
2117
|
async function createView(org, number, view, o) {
|
|
1799
2118
|
const body = {
|
|
1800
2119
|
name: view.name,
|
|
@@ -1889,10 +2208,127 @@ async function renameView(viewId, name, visibleFieldIds, o) {
|
|
|
1889
2208
|
);
|
|
1890
2209
|
}
|
|
1891
2210
|
|
|
2211
|
+
// src/core/github-issues.ts
|
|
2212
|
+
async function repository(owner, name, label, o) {
|
|
2213
|
+
const d = await graphqlFound(
|
|
2214
|
+
`query($owner: String!, $name: String!, $label: String!) {
|
|
2215
|
+
repository(owner: $owner, name: $name) { id hasIssuesEnabled label(name: $label) { id } }
|
|
2216
|
+
}`,
|
|
2217
|
+
{ owner, name, label },
|
|
2218
|
+
o
|
|
2219
|
+
);
|
|
2220
|
+
const r = d?.repository ?? null;
|
|
2221
|
+
return r === null ? null : { id: r.id, issues: r.hasIssuesEnabled, labelId: r.label?.id ?? null };
|
|
2222
|
+
}
|
|
2223
|
+
async function createLabel(owner, name, label, o) {
|
|
2224
|
+
const body = await postJson(
|
|
2225
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/labels`,
|
|
2226
|
+
label,
|
|
2227
|
+
o,
|
|
2228
|
+
true
|
|
2229
|
+
);
|
|
2230
|
+
const id = body !== null && typeof body === "object" ? body["node_id"] : void 0;
|
|
2231
|
+
if (typeof id !== "string")
|
|
2232
|
+
throw new Error(`GitHub created the label ${label.name} without a node id`);
|
|
2233
|
+
return id;
|
|
2234
|
+
}
|
|
2235
|
+
async function createIssue(repositoryId, issue, o) {
|
|
2236
|
+
const d = await graphql(
|
|
2237
|
+
`
|
|
2238
|
+
mutation (
|
|
2239
|
+
$repositoryId: ID!
|
|
2240
|
+
$title: String!
|
|
2241
|
+
$body: String
|
|
2242
|
+
$labelIds: [ID!]
|
|
2243
|
+
) {
|
|
2244
|
+
createIssue(
|
|
2245
|
+
input: {
|
|
2246
|
+
repositoryId: $repositoryId
|
|
2247
|
+
title: $title
|
|
2248
|
+
body: $body
|
|
2249
|
+
labelIds: $labelIds
|
|
2250
|
+
}
|
|
2251
|
+
) {
|
|
2252
|
+
issue {
|
|
2253
|
+
id
|
|
2254
|
+
number
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
`,
|
|
2259
|
+
{ repositoryId, ...issue },
|
|
2260
|
+
o
|
|
2261
|
+
);
|
|
2262
|
+
return d.createIssue.issue;
|
|
2263
|
+
}
|
|
2264
|
+
async function updateIssue(id, change, o) {
|
|
2265
|
+
await graphql(
|
|
2266
|
+
`
|
|
2267
|
+
mutation ($id: ID!, $title: String, $body: String) {
|
|
2268
|
+
updateIssue(input: { id: $id, title: $title, body: $body }) {
|
|
2269
|
+
issue {
|
|
2270
|
+
id
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
`,
|
|
2275
|
+
{ id, ...change },
|
|
2276
|
+
o
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
async function closeIssue(id, o) {
|
|
2280
|
+
await graphql(
|
|
2281
|
+
`
|
|
2282
|
+
mutation ($id: ID!) {
|
|
2283
|
+
closeIssue(input: { issueId: $id, stateReason: NOT_PLANNED }) {
|
|
2284
|
+
issue {
|
|
2285
|
+
id
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
`,
|
|
2290
|
+
{ id },
|
|
2291
|
+
o
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
async function reopenIssue(id, o) {
|
|
2295
|
+
await graphql(
|
|
2296
|
+
`
|
|
2297
|
+
mutation ($id: ID!) {
|
|
2298
|
+
reopenIssue(input: { issueId: $id }) {
|
|
2299
|
+
issue {
|
|
2300
|
+
id
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
`,
|
|
2305
|
+
{ id },
|
|
2306
|
+
o
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
async function addLabels(id, labelIds, o) {
|
|
2310
|
+
await graphql(
|
|
2311
|
+
`
|
|
2312
|
+
mutation ($id: ID!, $labelIds: [ID!]!) {
|
|
2313
|
+
addLabelsToLabelable(input: { labelableId: $id, labelIds: $labelIds }) {
|
|
2314
|
+
clientMutationId
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
`,
|
|
2318
|
+
{ id, labelIds },
|
|
2319
|
+
o
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
1892
2323
|
// src/core/github-board.ts
|
|
1893
2324
|
var PROJECT_TITLE = "Agent Pulse";
|
|
1894
2325
|
var WORKING = "working";
|
|
1895
2326
|
var WORKING_VIEW = "Working";
|
|
2327
|
+
var MEMORY = ".rness";
|
|
2328
|
+
var LABEL = "rness";
|
|
2329
|
+
var LABEL_COLOR = "5319e7";
|
|
2330
|
+
var LABEL_DESCRIPTION = "A document of .rness, on Agent Pulse";
|
|
2331
|
+
var labelled = (labels) => labels.some((l) => l.toLowerCase() === LABEL.toLowerCase());
|
|
1896
2332
|
var STATUS_FIELD = "Status";
|
|
1897
2333
|
var ALL_VIEW = "All";
|
|
1898
2334
|
var DEFAULT_VIEW = "View 1";
|
|
@@ -1921,6 +2357,8 @@ var orderedFirst = (wanted, all) => {
|
|
|
1921
2357
|
var GitHubBoards = class {
|
|
1922
2358
|
#o;
|
|
1923
2359
|
#cache = /* @__PURE__ */ new Map();
|
|
2360
|
+
/** Each organization's `.rness`, as first read: its id, its Issues, its label. */
|
|
2361
|
+
#memories = /* @__PURE__ */ new Map();
|
|
1924
2362
|
constructor(options) {
|
|
1925
2363
|
this.#o = options;
|
|
1926
2364
|
}
|
|
@@ -1943,6 +2381,23 @@ var GitHubBoards = class {
|
|
|
1943
2381
|
cache.fields ??= await fields(cache.projectId, this.#o);
|
|
1944
2382
|
return cache.fields;
|
|
1945
2383
|
}
|
|
2384
|
+
async #memory(org) {
|
|
2385
|
+
const known = this.#memories.get(org);
|
|
2386
|
+
if (known !== void 0) return known;
|
|
2387
|
+
const found = await repository(org, MEMORY, LABEL, this.#o);
|
|
2388
|
+
if (found === null)
|
|
2389
|
+
throw new Error(
|
|
2390
|
+
`the pulse needs ${org}/${MEMORY} on GitHub: it is not there, or this login cannot see it`
|
|
2391
|
+
);
|
|
2392
|
+
this.#memories.set(org, found);
|
|
2393
|
+
return found;
|
|
2394
|
+
}
|
|
2395
|
+
async checkIssues(org) {
|
|
2396
|
+
if (!(await this.#memory(org)).issues)
|
|
2397
|
+
throw new Error(
|
|
2398
|
+
`the pulse needs Issues on ${org}/${MEMORY}: turn them on in its Settings`
|
|
2399
|
+
);
|
|
2400
|
+
}
|
|
1946
2401
|
/** The project only: the caller declares it before `ensureLayout` builds it. */
|
|
1947
2402
|
async createBoard(org) {
|
|
1948
2403
|
const project = await createProject(
|
|
@@ -2079,89 +2534,186 @@ var GitHubBoards = class {
|
|
|
2079
2534
|
this.#o
|
|
2080
2535
|
);
|
|
2081
2536
|
}
|
|
2537
|
+
const memory = await this.#memory(board.org);
|
|
2538
|
+
if (memory.labelId === null) {
|
|
2539
|
+
memory.labelId = await createLabel(
|
|
2540
|
+
board.org,
|
|
2541
|
+
MEMORY,
|
|
2542
|
+
{ name: LABEL, color: LABEL_COLOR, description: LABEL_DESCRIPTION },
|
|
2543
|
+
this.#o
|
|
2544
|
+
);
|
|
2545
|
+
added.push(`label ${LABEL}`);
|
|
2546
|
+
}
|
|
2547
|
+
const repository2 = `${board.org}/${MEMORY}`;
|
|
2548
|
+
const linked = await linkedRepositories(cache.projectId, this.#o);
|
|
2549
|
+
if (!linked.some((r) => r.toLowerCase() === repository2.toLowerCase())) {
|
|
2550
|
+
await linkRepository(cache.projectId, memory.id, this.#o);
|
|
2551
|
+
added.push(`link ${repository2}`);
|
|
2552
|
+
}
|
|
2082
2553
|
cache.fields = [status2, collection, ...own.values(), agent, session, path];
|
|
2083
2554
|
return added;
|
|
2084
2555
|
}
|
|
2085
|
-
async items(board) {
|
|
2556
|
+
async items(board, options = {}) {
|
|
2086
2557
|
const cache = await this.#cacheOf(board);
|
|
2087
|
-
const raw = await listItems(cache.projectId, this.#o);
|
|
2558
|
+
const raw = await listItems(cache.projectId, this.#o, options);
|
|
2088
2559
|
cache.known = new Map(raw.map((r) => [r.id, r]));
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2560
|
+
const memory = `${board.org}/${MEMORY}`.toLowerCase();
|
|
2561
|
+
return raw.filter((r) => !r.archived).map((r) => {
|
|
2562
|
+
const issue = r.issue !== null && r.issue.repository.toLowerCase() === memory ? r.issue : null;
|
|
2563
|
+
const ours = r.draftId !== null || issue !== null;
|
|
2564
|
+
return {
|
|
2565
|
+
id: r.id,
|
|
2566
|
+
path: ours ? r.values["Path"] ?? null : null,
|
|
2567
|
+
issue: issue === null ? null : {
|
|
2568
|
+
number: issue.number,
|
|
2569
|
+
open: issue.open,
|
|
2570
|
+
labelled: labelled(issue.labels),
|
|
2571
|
+
body: issue.body
|
|
2572
|
+
},
|
|
2573
|
+
title: r.title,
|
|
2574
|
+
status: r.values[STATUS_FIELD] ?? null,
|
|
2575
|
+
collectionStatus: r.values[statusFieldName(r.values[COLLECTION_FIELD] ?? "")] ?? null,
|
|
2576
|
+
type: r.values[COLLECTION_FIELD] ?? null,
|
|
2577
|
+
agent: r.values["Agent"] ?? null,
|
|
2578
|
+
session: r.values["Session"] ?? null
|
|
2579
|
+
};
|
|
2580
|
+
});
|
|
2101
2581
|
}
|
|
2102
2582
|
async #set(board, itemId, name, value) {
|
|
2103
2583
|
const cache = await this.#cacheOf(board);
|
|
2104
2584
|
const field = (await this.#fields(board)).find((f) => f.name === name);
|
|
2105
2585
|
if (field === void 0) throw new Error(`the board has no field ${name}`);
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2586
|
+
let sent = null;
|
|
2587
|
+
if (value !== null && field.options === null) sent = { text: value };
|
|
2588
|
+
else if (value !== null) {
|
|
2589
|
+
const option = field.options?.find((opt) => opt.name === value);
|
|
2590
|
+
if (option === void 0)
|
|
2591
|
+
throw new Error(`the field ${name} has no option ${value}`);
|
|
2592
|
+
sent = { optionId: option.id };
|
|
2109
2593
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
{ text: value },
|
|
2116
|
-
this.#o
|
|
2117
|
-
);
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
const option = field.options.find((opt) => opt.name === value);
|
|
2121
|
-
if (option === void 0)
|
|
2122
|
-
throw new Error(`the field ${name} has no option ${value}`);
|
|
2123
|
-
await setValue(
|
|
2124
|
-
cache.projectId,
|
|
2125
|
-
itemId,
|
|
2126
|
-
field.id,
|
|
2127
|
-
{ optionId: option.id },
|
|
2128
|
-
this.#o
|
|
2129
|
-
);
|
|
2594
|
+
await setValue(cache.projectId, itemId, field.id, sent, this.#o);
|
|
2595
|
+
const known = cache.known.get(itemId);
|
|
2596
|
+
if (known === void 0) return;
|
|
2597
|
+
if (value === null) delete known.values[name];
|
|
2598
|
+
else known.values[name] = value;
|
|
2130
2599
|
}
|
|
2131
2600
|
async apply(board, step2) {
|
|
2132
|
-
if (step2.kind === "unchanged") return;
|
|
2601
|
+
if (step2.kind === "unchanged") return null;
|
|
2133
2602
|
const cache = await this.#cacheOf(board);
|
|
2134
|
-
if (step2.kind === "
|
|
2603
|
+
if (step2.kind === "create") return this.#create(board, cache, step2.want);
|
|
2604
|
+
const known = await this.#known(board, step2.id);
|
|
2605
|
+
if (step2.kind === "archive" || step2.kind === "close") {
|
|
2606
|
+
if (step2.kind === "close" && known.issue?.open === true)
|
|
2607
|
+
await closeIssue(known.issue.id, this.#o);
|
|
2135
2608
|
await archive(cache.projectId, step2.id, this.#o);
|
|
2136
2609
|
cache.known.delete(step2.id);
|
|
2137
|
-
return;
|
|
2610
|
+
return null;
|
|
2611
|
+
}
|
|
2612
|
+
if (step2.kind === "body") {
|
|
2613
|
+
const issue2 = this.#issueOf(known);
|
|
2614
|
+
await updateIssue(issue2.id, { body: step2.body }, this.#o);
|
|
2615
|
+
issue2.body = step2.body;
|
|
2616
|
+
return null;
|
|
2617
|
+
}
|
|
2618
|
+
const labelId = await this.#labelId(board);
|
|
2619
|
+
if (step2.kind === "convert") {
|
|
2620
|
+
const made = await convertDraft(
|
|
2621
|
+
step2.id,
|
|
2622
|
+
(await this.#memory(board.org)).id,
|
|
2623
|
+
this.#o
|
|
2624
|
+
);
|
|
2625
|
+
known.draftId = null;
|
|
2626
|
+
known.issue = {
|
|
2627
|
+
...made,
|
|
2628
|
+
open: true,
|
|
2629
|
+
repository: `${board.org}/${MEMORY}`,
|
|
2630
|
+
body: "",
|
|
2631
|
+
labels: []
|
|
2632
|
+
};
|
|
2633
|
+
await this.#issue(known, step2.want, labelId);
|
|
2634
|
+
await this.#values(board, step2.id, step2.want);
|
|
2635
|
+
return { id: step2.id, number: made.number };
|
|
2636
|
+
}
|
|
2637
|
+
const issue = this.#issueOf(known);
|
|
2638
|
+
if (step2.reopen) {
|
|
2639
|
+
await reopenIssue(issue.id, this.#o);
|
|
2640
|
+
issue.open = true;
|
|
2641
|
+
}
|
|
2642
|
+
await this.#issue(known, step2.want, labelId);
|
|
2643
|
+
await this.#values(board, step2.id, step2.want);
|
|
2644
|
+
return null;
|
|
2645
|
+
}
|
|
2646
|
+
/** A new document's issue: labelled, the first line as body, added to the board, Path first. */
|
|
2647
|
+
async #create(board, cache, want) {
|
|
2648
|
+
const memory = await this.#memory(board.org);
|
|
2649
|
+
const labelId = await this.#labelId(board);
|
|
2650
|
+
const made = await createIssue(
|
|
2651
|
+
memory.id,
|
|
2652
|
+
{ title: want.title, body: want.body, labelIds: [labelId] },
|
|
2653
|
+
this.#o
|
|
2654
|
+
);
|
|
2655
|
+
const id = await addItem(cache.projectId, made.id, this.#o);
|
|
2656
|
+
cache.known.set(id, {
|
|
2657
|
+
id,
|
|
2658
|
+
draftId: null,
|
|
2659
|
+
issue: {
|
|
2660
|
+
...made,
|
|
2661
|
+
open: true,
|
|
2662
|
+
repository: `${board.org}/${MEMORY}`,
|
|
2663
|
+
body: want.body,
|
|
2664
|
+
labels: [LABEL]
|
|
2665
|
+
},
|
|
2666
|
+
title: want.title,
|
|
2667
|
+
archived: false,
|
|
2668
|
+
values: {}
|
|
2669
|
+
});
|
|
2670
|
+
await this.#values(board, id, want);
|
|
2671
|
+
return { id, number: made.number };
|
|
2672
|
+
}
|
|
2673
|
+
/** The label and the title an issue of the board carries. */
|
|
2674
|
+
async #issue(known, want, labelId) {
|
|
2675
|
+
const issue = this.#issueOf(known);
|
|
2676
|
+
if (!labelled(issue.labels)) {
|
|
2677
|
+
await addLabels(issue.id, [labelId], this.#o);
|
|
2678
|
+
issue.labels.push(LABEL);
|
|
2138
2679
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2680
|
+
if (known.title !== want.title) {
|
|
2681
|
+
await updateIssue(issue.id, { title: want.title }, this.#o);
|
|
2682
|
+
known.title = want.title;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
/** Path first — an issue on the board without it is the team's — then the fields, each only when it differs. */
|
|
2686
|
+
async #values(board, id, want) {
|
|
2687
|
+
const values2 = (await this.#known(board, id)).values;
|
|
2688
|
+
const wanted = [
|
|
2141
2689
|
["Path", want.path],
|
|
2142
2690
|
[COLLECTION_FIELD, want.type],
|
|
2143
2691
|
[STATUS_FIELD, want.status],
|
|
2144
2692
|
...want.statusField === null ? [] : [[want.statusField, want.status]]
|
|
2145
2693
|
];
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2694
|
+
for (const [name, value] of wanted)
|
|
2695
|
+
if ((values2[name] ?? null) !== value)
|
|
2696
|
+
await this.#set(board, id, name, value);
|
|
2697
|
+
}
|
|
2698
|
+
async #known(board, id) {
|
|
2699
|
+
const cache = await this.#cacheOf(board);
|
|
2700
|
+
if (!cache.known.has(id)) await this.items(board);
|
|
2701
|
+
const known = cache.known.get(id);
|
|
2702
|
+
if (known === void 0) throw new Error(`the board has no item ${id}`);
|
|
2703
|
+
return known;
|
|
2704
|
+
}
|
|
2705
|
+
#issueOf(known) {
|
|
2706
|
+
if (known.issue === null)
|
|
2707
|
+
throw new Error(`the board's item ${known.id} is no issue`);
|
|
2708
|
+
return known.issue;
|
|
2709
|
+
}
|
|
2710
|
+
async #labelId(board) {
|
|
2711
|
+
const id = (await this.#memory(board.org)).labelId;
|
|
2712
|
+
if (id === null)
|
|
2713
|
+
throw new Error(
|
|
2714
|
+
`${board.org}/${MEMORY} has no label ${LABEL}: run rness pulse sync`
|
|
2152
2715
|
);
|
|
2153
|
-
|
|
2154
|
-
if (value !== null) await this.#set(board, id, name, value);
|
|
2155
|
-
return;
|
|
2156
|
-
}
|
|
2157
|
-
if (!cache.known.has(step2.id)) await this.items(board);
|
|
2158
|
-
const known = cache.known.get(step2.id);
|
|
2159
|
-
if (known?.draftId == null)
|
|
2160
|
-
throw new Error(`the board has no draft item ${step2.id}`);
|
|
2161
|
-
await editDraft(known.draftId, want.title, want.body, this.#o);
|
|
2162
|
-
for (const [name, value] of values2)
|
|
2163
|
-
if ((known.values[name] ?? null) !== value)
|
|
2164
|
-
await this.#set(board, step2.id, name, value);
|
|
2716
|
+
return id;
|
|
2165
2717
|
}
|
|
2166
2718
|
async mark(board, itemIds, session) {
|
|
2167
2719
|
for (const id of itemIds) {
|
|
@@ -2176,6 +2728,7 @@ var GITHUB_HTTPS = "https://github.com/";
|
|
|
2176
2728
|
var GitHubOAuthProvider = class {
|
|
2177
2729
|
#token;
|
|
2178
2730
|
#apiBase;
|
|
2731
|
+
#wait;
|
|
2179
2732
|
#login;
|
|
2180
2733
|
#boardClient;
|
|
2181
2734
|
get authenticated() {
|
|
@@ -2184,9 +2737,14 @@ var GitHubOAuthProvider = class {
|
|
|
2184
2737
|
constructor(options) {
|
|
2185
2738
|
this.#token = options.token;
|
|
2186
2739
|
this.#apiBase = options.apiBase;
|
|
2740
|
+
this.#wait = options.wait;
|
|
2187
2741
|
}
|
|
2188
2742
|
#api(token) {
|
|
2189
|
-
return
|
|
2743
|
+
return {
|
|
2744
|
+
token,
|
|
2745
|
+
...this.#apiBase === void 0 ? {} : { apiBase: this.#apiBase },
|
|
2746
|
+
...this.#wait === void 0 ? {} : { wait: this.#wait }
|
|
2747
|
+
};
|
|
2190
2748
|
}
|
|
2191
2749
|
identity() {
|
|
2192
2750
|
this.#login ??= (async () => {
|
|
@@ -2240,6 +2798,9 @@ var GitHubOAuthProvider = class {
|
|
|
2240
2798
|
return this.#boardClient;
|
|
2241
2799
|
}
|
|
2242
2800
|
// The board methods are async so that a missing login rejects, never throws.
|
|
2801
|
+
async checkIssues(org) {
|
|
2802
|
+
return this.#boards().checkIssues(org);
|
|
2803
|
+
}
|
|
2243
2804
|
async createBoard(org) {
|
|
2244
2805
|
return this.#boards().createBoard(org);
|
|
2245
2806
|
}
|
|
@@ -2249,8 +2810,8 @@ var GitHubOAuthProvider = class {
|
|
|
2249
2810
|
async ensureLayout(board, layout) {
|
|
2250
2811
|
return this.#boards().ensureLayout(board, layout);
|
|
2251
2812
|
}
|
|
2252
|
-
async items(board) {
|
|
2253
|
-
return this.#boards().items(board);
|
|
2813
|
+
async items(board, options) {
|
|
2814
|
+
return this.#boards().items(board, options);
|
|
2254
2815
|
}
|
|
2255
2816
|
async apply(board, step2) {
|
|
2256
2817
|
return this.#boards().apply(board, step2);
|
|
@@ -2263,16 +2824,17 @@ var GitHubOAuthProvider = class {
|
|
|
2263
2824
|
return { username: "x-access-token", password: this.#token.token };
|
|
2264
2825
|
}
|
|
2265
2826
|
};
|
|
2266
|
-
async function githubProvider(apiBase) {
|
|
2827
|
+
async function githubProvider(apiBase, wait) {
|
|
2267
2828
|
return new GitHubOAuthProvider({
|
|
2268
2829
|
token: await resolveToken(),
|
|
2269
|
-
...apiBase === void 0 ? {} : { apiBase }
|
|
2830
|
+
...apiBase === void 0 ? {} : { apiBase },
|
|
2831
|
+
...wait === void 0 ? {} : { wait }
|
|
2270
2832
|
});
|
|
2271
2833
|
}
|
|
2272
2834
|
|
|
2273
2835
|
// src/core/repos.ts
|
|
2274
2836
|
import { glob as glob2, readFile as readFile5, rm as rm3 } from "fs/promises";
|
|
2275
|
-
import { join as join8, posix as
|
|
2837
|
+
import { join as join8, posix as posix5, sep as sep2 } from "path";
|
|
2276
2838
|
async function addRepository(input) {
|
|
2277
2839
|
const { name, url } = parseRepoSpec(input.spec, input.org, input.host);
|
|
2278
2840
|
const dir = join8(input.root, "org", name);
|
|
@@ -2352,8 +2914,8 @@ async function workspaceDirs(repoDir) {
|
|
|
2352
2914
|
const dirs = /* @__PURE__ */ new Set();
|
|
2353
2915
|
for (const pattern of patterns) {
|
|
2354
2916
|
for await (const match of glob2(pattern, { cwd: repoDir })) {
|
|
2355
|
-
const rel = match.split(sep2).join(
|
|
2356
|
-
if (await exists(join8(repoDir, ...rel.split(
|
|
2917
|
+
const rel = match.split(sep2).join(posix5.sep);
|
|
2918
|
+
if (await exists(join8(repoDir, ...rel.split(posix5.sep), "package.json")))
|
|
2357
2919
|
dirs.add(rel);
|
|
2358
2920
|
}
|
|
2359
2921
|
}
|
|
@@ -2650,7 +3212,7 @@ function reportError(e) {
|
|
|
2650
3212
|
}
|
|
2651
3213
|
|
|
2652
3214
|
// src/core/block.ts
|
|
2653
|
-
import { createHash } from "crypto";
|
|
3215
|
+
import { createHash as createHash2 } from "crypto";
|
|
2654
3216
|
var BEGIN = "<!-- BEGIN rness -->";
|
|
2655
3217
|
var END = "<!-- END rness -->";
|
|
2656
3218
|
var BLOCK_SIZE_WARNING = 32 * 1024;
|
|
@@ -2659,7 +3221,7 @@ function toLf(s) {
|
|
|
2659
3221
|
return s.replace(/\r\n?/g, "\n");
|
|
2660
3222
|
}
|
|
2661
3223
|
function blockHash(body) {
|
|
2662
|
-
return
|
|
3224
|
+
return createHash2("sha256").update(toLf(body), "utf8").digest("hex").slice(0, 12);
|
|
2663
3225
|
}
|
|
2664
3226
|
function intro(scope, org, depth) {
|
|
2665
3227
|
if (scope === null) {
|
|
@@ -3059,8 +3621,8 @@ function wholeDiff(existing, content) {
|
|
|
3059
3621
|
async function filesOf(root, manifest, targets) {
|
|
3060
3622
|
const files = [];
|
|
3061
3623
|
if (!await exists(join11(root, "org"))) return files;
|
|
3062
|
-
for (const
|
|
3063
|
-
for (const owned of
|
|
3624
|
+
for (const target2 of targets)
|
|
3625
|
+
for (const owned of target2.files)
|
|
3064
3626
|
if (owned.at === "root")
|
|
3065
3627
|
files.push({
|
|
3066
3628
|
...owned,
|
|
@@ -3069,8 +3631,8 @@ async function filesOf(root, manifest, targets) {
|
|
|
3069
3631
|
});
|
|
3070
3632
|
for (const repo of Object.keys(manifest.repos)) {
|
|
3071
3633
|
if (!await exists(join11(root, "org", repo))) continue;
|
|
3072
|
-
for (const
|
|
3073
|
-
for (const owned of
|
|
3634
|
+
for (const target2 of targets)
|
|
3635
|
+
for (const owned of target2.files)
|
|
3074
3636
|
if (owned.at === "clones")
|
|
3075
3637
|
files.push({
|
|
3076
3638
|
...owned,
|
|
@@ -3183,7 +3745,7 @@ var PROVIDERS = {
|
|
|
3183
3745
|
name: "github",
|
|
3184
3746
|
label: "GitHub",
|
|
3185
3747
|
available: true,
|
|
3186
|
-
open: ({ apiBase }) => githubProvider(apiBase)
|
|
3748
|
+
open: ({ apiBase, wait }) => githubProvider(apiBase, wait)
|
|
3187
3749
|
},
|
|
3188
3750
|
gitlab: { name: "gitlab", label: "GitLab", available: false },
|
|
3189
3751
|
atlassian: {
|
|
@@ -3885,7 +4447,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
3885
4447
|
}
|
|
3886
4448
|
|
|
3887
4449
|
// src/commands/context.ts
|
|
3888
|
-
import { posix as
|
|
4450
|
+
import { posix as posix6, relative, sep as sep3 } from "path";
|
|
3889
4451
|
function renderMarkdown(ctx) {
|
|
3890
4452
|
const parts = [];
|
|
3891
4453
|
for (const collection of ctx.collections) {
|
|
@@ -3917,7 +4479,7 @@ async function contextCommand(opts) {
|
|
|
3917
4479
|
}
|
|
3918
4480
|
scope = opts.scope;
|
|
3919
4481
|
} else {
|
|
3920
|
-
const rel = relative(ws.root, cwd).split(sep3).join(
|
|
4482
|
+
const rel = relative(ws.root, cwd).split(sep3).join(posix6.sep);
|
|
3921
4483
|
scope = resolveScope(manifest, rel);
|
|
3922
4484
|
}
|
|
3923
4485
|
const ctx = await assembleContext({
|
|
@@ -5782,9 +6344,9 @@ async function clonesToCommit(root, cwd, manifest) {
|
|
|
5782
6344
|
}
|
|
5783
6345
|
return clones;
|
|
5784
6346
|
}
|
|
5785
|
-
function finish(ui, cwd, rnessDir, label,
|
|
6347
|
+
function finish(ui, cwd, rnessDir, label, target2, ending) {
|
|
5786
6348
|
const rel = relative5(cwd, rnessDir) || ".";
|
|
5787
|
-
const message2 = `chore: rness ${
|
|
6349
|
+
const message2 = `chore: rness ${target2}`;
|
|
5788
6350
|
const next = [];
|
|
5789
6351
|
if (ending.commit === "committed") next.push(`git -C ${rel} push`);
|
|
5790
6352
|
if (ending.commit === "refused")
|
|
@@ -5797,12 +6359,12 @@ function finish(ui, cwd, rnessDir, label, target, ending) {
|
|
|
5797
6359
|
next.push("# teammates: git pull \u2014 the next rness command installs it");
|
|
5798
6360
|
if (ui.session) {
|
|
5799
6361
|
if (next.length > 0) ui.note("Next steps", next);
|
|
5800
|
-
ui.outro(`${label} runs @rness/cli ${
|
|
6362
|
+
ui.outro(`${label} runs @rness/cli ${target2}`);
|
|
5801
6363
|
} else
|
|
5802
6364
|
process.stdout.write(
|
|
5803
6365
|
[
|
|
5804
6366
|
"",
|
|
5805
|
-
status("upgraded", `${label} to @rness/cli ${
|
|
6367
|
+
status("upgraded", `${label} to @rness/cli ${target2}`),
|
|
5806
6368
|
"",
|
|
5807
6369
|
...next.length > 0 ? ["Next:", ...next.map((line3) => ` ${line3}`), ""] : []
|
|
5808
6370
|
].join("\n")
|
|
@@ -5838,12 +6400,12 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
5838
6400
|
}
|
|
5839
6401
|
const ui = interactive ? await makeUi(terminal) : { ...plainUi };
|
|
5840
6402
|
ui.intro(`Upgrade workspace ${basename5(ws.root)}`);
|
|
5841
|
-
const
|
|
6403
|
+
const target2 = version ?? await ui.step(
|
|
5842
6404
|
{ doing: "asking the registry for the latest release", quiet: true },
|
|
5843
6405
|
() => latestVersion(),
|
|
5844
6406
|
(v) => v === null ? null : ["latest", `@rness/cli ${v}`]
|
|
5845
6407
|
);
|
|
5846
|
-
if (
|
|
6408
|
+
if (target2 === null) {
|
|
5847
6409
|
process.stderr.write(
|
|
5848
6410
|
`cannot reach the registry; name the version: ${rnessCommand()} upgrade <version>
|
|
5849
6411
|
`
|
|
@@ -5871,44 +6433,44 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
5871
6433
|
return 1;
|
|
5872
6434
|
}
|
|
5873
6435
|
const base = await findScaffoldBase(ws.rnessDir);
|
|
5874
|
-
const scaffoldCurrent = base !== null && base.version ===
|
|
5875
|
-
if (pin.spec ===
|
|
5876
|
-
if (ui.session) ui.outro(`Already at ${
|
|
5877
|
-
else process.stdout.write(`already at ${
|
|
6436
|
+
const scaffoldCurrent = base !== null && base.version === target2;
|
|
6437
|
+
if (pin.spec === target2 && installed === target2 && scaffoldCurrent) {
|
|
6438
|
+
if (ui.session) ui.outro(`Already at ${target2}`);
|
|
6439
|
+
else process.stdout.write(`already at ${target2}
|
|
5878
6440
|
`);
|
|
5879
6441
|
return 0;
|
|
5880
6442
|
}
|
|
5881
|
-
const downgrading = EXACT_VERSION.test(pin.spec) && compareVersions(
|
|
6443
|
+
const downgrading = EXACT_VERSION.test(pin.spec) && compareVersions(target2, pin.spec) < 0;
|
|
5882
6444
|
if (downgrading && version === void 0) {
|
|
5883
6445
|
process.stderr.write(
|
|
5884
|
-
`${label} pins @rness/cli ${pin.spec}, newer than the latest release ${
|
|
6446
|
+
`${label} pins @rness/cli ${pin.spec}, newer than the latest release ${target2}; to downgrade: ${rnessCommand()} upgrade ${target2}
|
|
5885
6447
|
`
|
|
5886
6448
|
);
|
|
5887
6449
|
return 1;
|
|
5888
6450
|
}
|
|
5889
6451
|
const pm = await workspacePackageManager(ws.rnessDir);
|
|
5890
|
-
if (pin.spec ===
|
|
6452
|
+
if (pin.spec === target2 && installed === target2)
|
|
5891
6453
|
ui.line(
|
|
5892
6454
|
"scaffold",
|
|
5893
|
-
`@rness/cli ${
|
|
5894
|
-
`Merging the @rness/cli ${
|
|
6455
|
+
`@rness/cli ${target2} \u2014 merging it into ${label}`,
|
|
6456
|
+
`Merging the @rness/cli ${target2} scaffold into ${label}`
|
|
5895
6457
|
);
|
|
5896
|
-
else if (pin.spec ===
|
|
6458
|
+
else if (pin.spec === target2)
|
|
5897
6459
|
ui.line(
|
|
5898
6460
|
"install",
|
|
5899
|
-
`@rness/cli ${
|
|
6461
|
+
`@rness/cli ${target2} in ${label} (${pm}) \u2014 ${installed ?? "nothing"} is installed`
|
|
5900
6462
|
);
|
|
5901
6463
|
else {
|
|
5902
6464
|
ui.line(
|
|
5903
6465
|
"upgrade",
|
|
5904
|
-
`@rness/cli ${pin.spec} \u2192 ${
|
|
6466
|
+
`@rness/cli ${pin.spec} \u2192 ${target2} in ${label} (${pm})${downgrading ? " \u2014 downgrading" : ""}`
|
|
5905
6467
|
);
|
|
5906
6468
|
ui.line("notes", NOTES, `Release notes: ${NOTES}`);
|
|
5907
6469
|
}
|
|
5908
6470
|
if (interactive) {
|
|
5909
6471
|
const p = await terminal.prompts();
|
|
5910
6472
|
const ok = await p.confirm({
|
|
5911
|
-
message: `Upgrade @rness/cli to ${
|
|
6473
|
+
message: `Upgrade @rness/cli to ${target2}?`
|
|
5912
6474
|
});
|
|
5913
6475
|
if (p.isCancel(ok) || ok !== true) {
|
|
5914
6476
|
ui.cancelled();
|
|
@@ -5918,31 +6480,31 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
5918
6480
|
let merged = false;
|
|
5919
6481
|
if (!scaffoldCurrent) {
|
|
5920
6482
|
const source = await (deps.scaffoldFor ?? scaffoldFor)(
|
|
5921
|
-
|
|
6483
|
+
target2,
|
|
5922
6484
|
ws.rnessDir
|
|
5923
6485
|
);
|
|
5924
6486
|
try {
|
|
5925
6487
|
const files = await renderScaffold(source.dir, {
|
|
5926
|
-
version:
|
|
6488
|
+
version: target2,
|
|
5927
6489
|
packageManager: await declaredManager(ws.rnessDir, pm)
|
|
5928
6490
|
});
|
|
5929
6491
|
if (base === null)
|
|
5930
6492
|
ui.line(
|
|
5931
6493
|
"adopting",
|
|
5932
|
-
`the @rness/cli ${
|
|
5933
|
-
`Adopting the @rness/cli ${
|
|
6494
|
+
`the @rness/cli ${target2} scaffold (no scaffold commit in ${label} yet)`,
|
|
6495
|
+
`Adopting the @rness/cli ${target2} scaffold \u2014 ${label} has no scaffold commit yet`
|
|
5934
6496
|
);
|
|
5935
6497
|
else
|
|
5936
6498
|
ui.line(
|
|
5937
6499
|
"merging",
|
|
5938
|
-
`the @rness/cli ${
|
|
5939
|
-
`Merging the @rness/cli ${
|
|
6500
|
+
`the @rness/cli ${target2} scaffold`,
|
|
6501
|
+
`Merging the @rness/cli ${target2} scaffold`
|
|
5940
6502
|
);
|
|
5941
6503
|
const commit = await buildScaffoldCommit(
|
|
5942
6504
|
ws.rnessDir,
|
|
5943
6505
|
base?.commit ?? null,
|
|
5944
6506
|
base === null ? await withoutRedundantKeeps(ws.rnessDir, files) : files,
|
|
5945
|
-
|
|
6507
|
+
target2
|
|
5946
6508
|
);
|
|
5947
6509
|
const result = await mergeScaffold(
|
|
5948
6510
|
ws.rnessDir,
|
|
@@ -5969,9 +6531,9 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
5969
6531
|
[packageFile, await readFile9(packageFile, "utf8")]
|
|
5970
6532
|
]);
|
|
5971
6533
|
const moved = await readPin(ws.rnessDir);
|
|
5972
|
-
if (moved !== null && moved.spec !==
|
|
5973
|
-
await writePin(ws.rnessDir, moved.spec,
|
|
5974
|
-
const mustInstall = !(pin.spec ===
|
|
6534
|
+
if (moved !== null && moved.spec !== target2)
|
|
6535
|
+
await writePin(ws.rnessDir, moved.spec, target2);
|
|
6536
|
+
const mustInstall = !(pin.spec === target2 && installed === target2);
|
|
5975
6537
|
if (mustInstall) {
|
|
5976
6538
|
try {
|
|
5977
6539
|
await ui.step(
|
|
@@ -6002,9 +6564,9 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
6002
6564
|
return 1;
|
|
6003
6565
|
}
|
|
6004
6566
|
const now = (await readPinnedCli(ws.rnessDir))?.version ?? null;
|
|
6005
|
-
if (now !==
|
|
6567
|
+
if (now !== target2) {
|
|
6006
6568
|
process.stderr.write(
|
|
6007
|
-
`${pm} install left @rness/cli ${now ?? "not installed"} in ${label}, not ${
|
|
6569
|
+
`${pm} install left @rness/cli ${now ?? "not installed"} in ${label}, not ${target2}
|
|
6008
6570
|
`
|
|
6009
6571
|
);
|
|
6010
6572
|
return 1;
|
|
@@ -6013,7 +6575,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
6013
6575
|
ui.gitIsSilent = true;
|
|
6014
6576
|
const code = await syncBlocks(ui, ws.root, basename5(ws.root));
|
|
6015
6577
|
if (code !== 0) return code;
|
|
6016
|
-
const message2 = `chore: rness ${
|
|
6578
|
+
const message2 = `chore: rness ${target2}`;
|
|
6017
6579
|
const committed = await commitUpgrade(ws.rnessDir, message2);
|
|
6018
6580
|
if (committed.kind === "committed")
|
|
6019
6581
|
ui.line(
|
|
@@ -6028,7 +6590,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
6028
6590
|
for (const line3 of committed.output.split("\n"))
|
|
6029
6591
|
if (line3.trim() !== "") ui.hint(line3, "stderr");
|
|
6030
6592
|
}
|
|
6031
|
-
return finish(ui, cwd, ws.rnessDir, label,
|
|
6593
|
+
return finish(ui, cwd, ws.rnessDir, label, target2, {
|
|
6032
6594
|
commit: committed.kind,
|
|
6033
6595
|
clones: await clonesToCommit(
|
|
6034
6596
|
ws.root,
|
|
@@ -6153,11 +6715,11 @@ password=${resolved.token}
|
|
|
6153
6715
|
|
|
6154
6716
|
// src/commands/hook.ts
|
|
6155
6717
|
import { realpath as realpath4 } from "fs/promises";
|
|
6156
|
-
import { isAbsolute as isAbsolute2, posix as
|
|
6718
|
+
import { isAbsolute as isAbsolute2, posix as posix8, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
6157
6719
|
|
|
6158
6720
|
// src/mcp/tools.ts
|
|
6159
6721
|
import { readFile as readFile10, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
6160
|
-
import { isAbsolute, join as join24, posix as
|
|
6722
|
+
import { isAbsolute, join as join24, posix as posix7, relative as relative6, resolve as resolve6, sep as sep4 } from "path";
|
|
6161
6723
|
var LABELS = {
|
|
6162
6724
|
standards: "Standards",
|
|
6163
6725
|
adr: "Decisions",
|
|
@@ -6197,7 +6759,7 @@ async function documents(rnessDir, only) {
|
|
|
6197
6759
|
scope: ownerScope(item.rel),
|
|
6198
6760
|
id,
|
|
6199
6761
|
status: typeof status2 === "string" ? status2 : null,
|
|
6200
|
-
title: documentTitle(item.title ??
|
|
6762
|
+
title: documentTitle(item.title ?? posix7.basename(item.rel, ".md"), id),
|
|
6201
6763
|
text: item.body
|
|
6202
6764
|
});
|
|
6203
6765
|
}
|
|
@@ -6227,7 +6789,7 @@ async function context(cwd, args) {
|
|
|
6227
6789
|
} else
|
|
6228
6790
|
scope = resolveScope(
|
|
6229
6791
|
manifest,
|
|
6230
|
-
relative6(ws.root, cwd).split(sep4).join(
|
|
6792
|
+
relative6(ws.root, cwd).split(sep4).join(posix7.sep)
|
|
6231
6793
|
);
|
|
6232
6794
|
const summary = await scopeSummary(ws, manifest, scope);
|
|
6233
6795
|
return {
|
|
@@ -6416,8 +6978,9 @@ function mcpTools(cwd) {
|
|
|
6416
6978
|
|
|
6417
6979
|
// src/pulse/detached.ts
|
|
6418
6980
|
import { spawn as nodeSpawn } from "child_process";
|
|
6419
|
-
import { mkdir as mkdir7, readFile as readFile11, rm as rm8, writeFile as writeFile4 } from "fs/promises";
|
|
6981
|
+
import { mkdir as mkdir7, open as open2, readFile as readFile11, rm as rm8, stat as stat5, writeFile as writeFile4 } from "fs/promises";
|
|
6420
6982
|
import { join as join25 } from "path";
|
|
6983
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
6421
6984
|
var detached = (args, cwd) => {
|
|
6422
6985
|
const bin = process.argv[1];
|
|
6423
6986
|
if (bin === void 0) return;
|
|
@@ -6452,6 +7015,33 @@ async function takeFailure() {
|
|
|
6452
7015
|
return null;
|
|
6453
7016
|
}
|
|
6454
7017
|
}
|
|
7018
|
+
var PULSE_LOCK_STALE_MS = 15 * 6e4;
|
|
7019
|
+
async function oneSyncAtATime(key, work, options = {}) {
|
|
7020
|
+
await mkdir7(configDir(), { recursive: true, mode: 448 });
|
|
7021
|
+
const file = join25(configDir(), `pulse-${key}.lock`);
|
|
7022
|
+
for (; ; ) {
|
|
7023
|
+
try {
|
|
7024
|
+
const handle = await open2(file, "wx");
|
|
7025
|
+
await handle.close();
|
|
7026
|
+
break;
|
|
7027
|
+
} catch (e) {
|
|
7028
|
+
if (e.code !== "EEXIST") throw e;
|
|
7029
|
+
const age = await stat5(file).then(
|
|
7030
|
+
(s) => Date.now() - s.mtimeMs,
|
|
7031
|
+
() => 0
|
|
7032
|
+
);
|
|
7033
|
+
if (age > PULSE_LOCK_STALE_MS) await rm8(file, { force: true });
|
|
7034
|
+
else if (options.wait !== true) return false;
|
|
7035
|
+
else await (options.sleep ?? ((ms) => delay2(ms)))(1e3);
|
|
7036
|
+
}
|
|
7037
|
+
}
|
|
7038
|
+
try {
|
|
7039
|
+
await work();
|
|
7040
|
+
return true;
|
|
7041
|
+
} finally {
|
|
7042
|
+
await rm8(file, { force: true });
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
6455
7045
|
|
|
6456
7046
|
// src/commands/hook.ts
|
|
6457
7047
|
var NOUNS = {
|
|
@@ -6479,7 +7069,7 @@ function cwdOf(input, env) {
|
|
|
6479
7069
|
if (typeof cwd === "string" && cwd !== "") return cwd;
|
|
6480
7070
|
return env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
|
|
6481
7071
|
}
|
|
6482
|
-
var toPosix2 = (p) => p.split(sep5).join(
|
|
7072
|
+
var toPosix2 = (p) => p.split(sep5).join(posix8.sep);
|
|
6483
7073
|
function count(n, [one, many]) {
|
|
6484
7074
|
return `${n} ${n === 1 ? one : many}`;
|
|
6485
7075
|
}
|
|
@@ -6823,29 +7413,162 @@ async function mcpCommand(opts, io = {
|
|
|
6823
7413
|
return 0;
|
|
6824
7414
|
}
|
|
6825
7415
|
|
|
7416
|
+
// src/commands/pulse.ts
|
|
7417
|
+
import { readFile as readFile13, readdir as readdir4 } from "fs/promises";
|
|
7418
|
+
import { join as join26 } from "path";
|
|
7419
|
+
import { setTimeout as delay3 } from "timers/promises";
|
|
7420
|
+
|
|
7421
|
+
// src/pulse/opened.ts
|
|
7422
|
+
import { mkdir as mkdir8, readFile as readFile12, rename as rename3, rm as rm9, writeFile as writeFile5 } from "fs/promises";
|
|
7423
|
+
import { dirname as dirname9 } from "path";
|
|
7424
|
+
var NAME2 = "rness/opened";
|
|
7425
|
+
function openedFile(rnessDir) {
|
|
7426
|
+
return gitPath(rnessDir, NAME2);
|
|
7427
|
+
}
|
|
7428
|
+
var NUMBER = /^[1-9][0-9]*$/;
|
|
7429
|
+
async function readOpened(file) {
|
|
7430
|
+
const opened = /* @__PURE__ */ new Map();
|
|
7431
|
+
if (file === null) return opened;
|
|
7432
|
+
let data;
|
|
7433
|
+
try {
|
|
7434
|
+
data = JSON.parse(await readFile12(file, "utf8"));
|
|
7435
|
+
} catch {
|
|
7436
|
+
return opened;
|
|
7437
|
+
}
|
|
7438
|
+
if (data === null || typeof data !== "object" || Array.isArray(data))
|
|
7439
|
+
return opened;
|
|
7440
|
+
for (const [key, path] of Object.entries(data))
|
|
7441
|
+
if (NUMBER.test(key) && typeof path === "string" && path !== "")
|
|
7442
|
+
opened.set(Number(key), path);
|
|
7443
|
+
return opened;
|
|
7444
|
+
}
|
|
7445
|
+
async function writeOpened(file, opened) {
|
|
7446
|
+
if (file === null) return;
|
|
7447
|
+
try {
|
|
7448
|
+
if (opened.size === 0) {
|
|
7449
|
+
await rm9(file, { force: true });
|
|
7450
|
+
return;
|
|
7451
|
+
}
|
|
7452
|
+
const entries = [...opened].sort(([a], [b]) => a - b);
|
|
7453
|
+
await mkdir8(dirname9(file), { recursive: true });
|
|
7454
|
+
const temporary = `${file}.${process.pid}`;
|
|
7455
|
+
await writeFile5(
|
|
7456
|
+
temporary,
|
|
7457
|
+
`${JSON.stringify(Object.fromEntries(entries), null, 2)}
|
|
7458
|
+
`
|
|
7459
|
+
);
|
|
7460
|
+
await rename3(temporary, file);
|
|
7461
|
+
} catch {
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7464
|
+
|
|
6826
7465
|
// src/pulse/plan.ts
|
|
6827
|
-
|
|
7466
|
+
var differs = (item, w) => item.title !== w.title || item.status !== w.status || w.statusField !== null && item.collectionStatus !== w.status || item.type !== w.type;
|
|
7467
|
+
var rank = (i) => i.issue === null ? 2 : i.issue.open ? 0 : 1;
|
|
7468
|
+
var keptFirst = (a, b) => rank(a) - rank(b) || (a.issue?.number ?? 0) - (b.issue?.number ?? 0);
|
|
7469
|
+
var gone = (item) => item.issue === null ? { kind: "archive", id: item.id } : { kind: "close", id: item.id };
|
|
7470
|
+
var firstLine5 = (body) => body.split(/\r\n?|\n/, 1)[0] ?? "";
|
|
7471
|
+
function strays(have) {
|
|
7472
|
+
const byLine = /* @__PURE__ */ new Map();
|
|
7473
|
+
for (const item of have) {
|
|
7474
|
+
if (item.path !== null || item.issue === null || item.issue.body === null || !item.issue.labelled)
|
|
7475
|
+
continue;
|
|
7476
|
+
const line3 = firstLine5(item.issue.body);
|
|
7477
|
+
byLine.set(line3, [...byLine.get(line3) ?? [], item]);
|
|
7478
|
+
}
|
|
7479
|
+
for (const items of byLine.values()) items.sort(keptFirst);
|
|
7480
|
+
return byLine;
|
|
7481
|
+
}
|
|
7482
|
+
function unwantedPaths(want, have) {
|
|
7483
|
+
const wanted = new Set(want.map((w) => w.path));
|
|
7484
|
+
const paths = /* @__PURE__ */ new Set();
|
|
7485
|
+
for (const item of have)
|
|
7486
|
+
if (item.path !== null && !wanted.has(item.path)) paths.add(item.path);
|
|
7487
|
+
return [...paths];
|
|
7488
|
+
}
|
|
7489
|
+
function isGone(path, item, seen, opened) {
|
|
7490
|
+
return seen.has(path) || item.issue !== null && opened.get(item.issue.number) === path;
|
|
7491
|
+
}
|
|
7492
|
+
function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map()) {
|
|
6828
7493
|
const byPath = /* @__PURE__ */ new Map();
|
|
6829
|
-
const
|
|
7494
|
+
for (const item of have)
|
|
7495
|
+
if (item.path !== null)
|
|
7496
|
+
byPath.set(item.path, [...byPath.get(item.path) ?? [], item]);
|
|
7497
|
+
const kept = /* @__PURE__ */ new Map();
|
|
6830
7498
|
const surplus = [];
|
|
6831
|
-
for (const
|
|
6832
|
-
|
|
6833
|
-
if (
|
|
6834
|
-
|
|
7499
|
+
for (const [path, items] of byPath) {
|
|
7500
|
+
const [keep, ...rest] = [...items].sort(keptFirst);
|
|
7501
|
+
if (keep !== void 0) kept.set(path, keep);
|
|
7502
|
+
surplus.push(...rest.map(gone));
|
|
6835
7503
|
}
|
|
7504
|
+
const left = strays(have);
|
|
7505
|
+
const steps = [];
|
|
6836
7506
|
const wanted = /* @__PURE__ */ new Set();
|
|
6837
7507
|
for (const w of want) {
|
|
6838
7508
|
wanted.add(w.path);
|
|
6839
|
-
const item =
|
|
7509
|
+
const item = kept.get(w.path) ?? left.get(w.body)?.[0];
|
|
6840
7510
|
if (item === void 0) steps.push({ kind: "create", want: w });
|
|
6841
|
-
else if (item.
|
|
6842
|
-
steps.push({ kind: "
|
|
7511
|
+
else if (item.issue === null)
|
|
7512
|
+
steps.push({ kind: "convert", id: item.id, want: w });
|
|
7513
|
+
else if (item.path === null || !item.issue.open || !item.issue.labelled || differs(item, w))
|
|
7514
|
+
steps.push({
|
|
7515
|
+
kind: "update",
|
|
7516
|
+
id: item.id,
|
|
7517
|
+
want: w,
|
|
7518
|
+
reopen: !item.issue.open
|
|
7519
|
+
});
|
|
6843
7520
|
else steps.push({ kind: "unchanged", id: item.id });
|
|
6844
7521
|
}
|
|
6845
|
-
for (const [path, item] of
|
|
6846
|
-
if (!wanted.has(path))
|
|
7522
|
+
for (const [path, item] of kept)
|
|
7523
|
+
if (!wanted.has(path))
|
|
7524
|
+
steps.push(
|
|
7525
|
+
isGone(path, item, seen, opened) ? gone(item) : { kind: "unchanged", id: item.id }
|
|
7526
|
+
);
|
|
6847
7527
|
return [...steps, ...surplus];
|
|
6848
7528
|
}
|
|
7529
|
+
function stillOpened(opened, have, seen, closed, created) {
|
|
7530
|
+
const items = /* @__PURE__ */ new Map();
|
|
7531
|
+
for (const item of have)
|
|
7532
|
+
if (item.issue !== null) items.set(item.issue.number, item.id);
|
|
7533
|
+
const next = /* @__PURE__ */ new Map();
|
|
7534
|
+
for (const [number, path] of opened) {
|
|
7535
|
+
const id = items.get(number);
|
|
7536
|
+
if (id !== void 0 && !closed.has(id) && !seen.has(path))
|
|
7537
|
+
next.set(number, path);
|
|
7538
|
+
}
|
|
7539
|
+
for (const [path, placed] of created) next.set(placed.number, path);
|
|
7540
|
+
return next;
|
|
7541
|
+
}
|
|
7542
|
+
function issuedAfter(steps, have, placed) {
|
|
7543
|
+
const byId = new Map(have.map((i) => [i.id, i]));
|
|
7544
|
+
const issued = /* @__PURE__ */ new Map();
|
|
7545
|
+
for (const step2 of steps) {
|
|
7546
|
+
if (step2.kind === "create" || step2.kind === "convert") {
|
|
7547
|
+
const p = placed.get(step2.want.path);
|
|
7548
|
+
if (p !== void 0) issued.set(step2.want.path, { ...p, body: null });
|
|
7549
|
+
} else if (step2.kind === "update" || step2.kind === "unchanged") {
|
|
7550
|
+
const item = byId.get(step2.id);
|
|
7551
|
+
const path = step2.kind === "update" ? step2.want.path : item?.path;
|
|
7552
|
+
if (item === void 0 || path == null || item.issue === null) continue;
|
|
7553
|
+
issued.set(path, {
|
|
7554
|
+
id: item.id,
|
|
7555
|
+
number: item.issue.number,
|
|
7556
|
+
body: item.issue.body
|
|
7557
|
+
});
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
return issued;
|
|
7561
|
+
}
|
|
7562
|
+
function planBodies(bodies, issued) {
|
|
7563
|
+
const steps = [];
|
|
7564
|
+
for (const [path, body] of bodies) {
|
|
7565
|
+
const i = issued.get(path);
|
|
7566
|
+
if (i === void 0) continue;
|
|
7567
|
+
if (i.body === null || digestOf(i.body) !== digestOf(body))
|
|
7568
|
+
steps.push({ kind: "body", id: i.id, body });
|
|
7569
|
+
}
|
|
7570
|
+
return steps;
|
|
7571
|
+
}
|
|
6849
7572
|
|
|
6850
7573
|
// src/commands/pulse.ts
|
|
6851
7574
|
var PROJECT_SCOPE = "project";
|
|
@@ -6861,15 +7584,23 @@ async function fromGithub(work) {
|
|
|
6861
7584
|
throw e;
|
|
6862
7585
|
}
|
|
6863
7586
|
}
|
|
6864
|
-
|
|
7587
|
+
var apiOf = (opts) => opts.githubApi === void 0 ? {} : { apiBase: opts.githubApi };
|
|
7588
|
+
var providerOptions = (opts, wait) => ({
|
|
7589
|
+
...apiOf(opts),
|
|
7590
|
+
...wait === void 0 ? {} : { wait }
|
|
7591
|
+
});
|
|
7592
|
+
async function context2(opts, wait) {
|
|
6865
7593
|
const ws = await findWorkspace(opts.cwd ?? process.cwd());
|
|
6866
7594
|
const manifest = await loadManifest(ws.rnessDir);
|
|
6867
|
-
const provider = await openProvider(manifest,
|
|
7595
|
+
const provider = await openProvider(manifest, providerOptions(opts, wait));
|
|
6868
7596
|
if (manifest.org === null)
|
|
6869
7597
|
throw new Error('the pulse needs an organization: rness.json has no "org"');
|
|
6870
7598
|
return { rnessDir: ws.rnessDir, manifest, org: manifest.org, provider };
|
|
6871
7599
|
}
|
|
6872
|
-
var
|
|
7600
|
+
var waitSaid = (opts, ui) => rateWait(async (ms) => {
|
|
7601
|
+
ui.line("waiting", `${Math.round(ms / 1e3)} s \u2014 GitHub's rate limit`);
|
|
7602
|
+
await (opts.sleep ?? ((n) => delay3(n)))(ms);
|
|
7603
|
+
});
|
|
6873
7604
|
async function missingAccess(provider) {
|
|
6874
7605
|
if (!provider.authenticated) return NEEDS_LOGIN;
|
|
6875
7606
|
const scopes = await fromGithub(provider.scopes());
|
|
@@ -6895,33 +7626,131 @@ var createdByKind = (ui, added) => {
|
|
|
6895
7626
|
const names = added.filter((a) => a.startsWith(`${kind} `)).map((a) => a.slice(kind.length + 1));
|
|
6896
7627
|
if (names.length > 0) ui.line("created", `${kind}s ${names.join(", ")}`);
|
|
6897
7628
|
}
|
|
7629
|
+
for (const a of added) {
|
|
7630
|
+
if (a.startsWith("label ")) ui.line("created", a);
|
|
7631
|
+
else if (a.startsWith("link "))
|
|
7632
|
+
ui.line("linked", `Agent Pulse to ${a.slice("link ".length)}`);
|
|
7633
|
+
}
|
|
6898
7634
|
};
|
|
7635
|
+
async function filesOf2(rnessDir) {
|
|
7636
|
+
const files = /* @__PURE__ */ new Set();
|
|
7637
|
+
const walk = async (rel) => {
|
|
7638
|
+
const dir = rel === "" ? rnessDir : join26(rnessDir, ...rel.split("/"));
|
|
7639
|
+
for (const e of await readdir4(dir, { withFileTypes: true })) {
|
|
7640
|
+
const path = rel === "" ? e.name : `${rel}/${e.name}`;
|
|
7641
|
+
if (e.isDirectory()) {
|
|
7642
|
+
if (e.name !== ".git" && e.name !== "node_modules") await walk(path);
|
|
7643
|
+
} else if (e.isFile()) files.add(path);
|
|
7644
|
+
}
|
|
7645
|
+
};
|
|
7646
|
+
await walk("");
|
|
7647
|
+
return files;
|
|
7648
|
+
}
|
|
7649
|
+
var count2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
7650
|
+
function sayDone(ui, org, documents2, steps, writes) {
|
|
7651
|
+
const n = (kind) => steps.filter((s) => s.kind === kind).length;
|
|
7652
|
+
const reopened = steps.filter((s) => s.kind === "update" && s.reopen).length;
|
|
7653
|
+
if (n("create") > 0) ui.line("created", count2(n("create"), "issue"));
|
|
7654
|
+
if (n("convert") > 0)
|
|
7655
|
+
ui.line(
|
|
7656
|
+
"converted",
|
|
7657
|
+
`${count2(n("convert"), "draft")} into issues of ${org}/.rness`
|
|
7658
|
+
);
|
|
7659
|
+
if (reopened > 0) ui.line("reopened", count2(reopened, "issue"));
|
|
7660
|
+
const written = new Set(writes.map((w) => w.id));
|
|
7661
|
+
const bodyOnly = steps.filter(
|
|
7662
|
+
(s) => s.kind === "unchanged" && written.has(s.id)
|
|
7663
|
+
).length;
|
|
7664
|
+
const parts = [
|
|
7665
|
+
["created", n("create")],
|
|
7666
|
+
["converted", n("convert")],
|
|
7667
|
+
["updated", n("update") + bodyOnly],
|
|
7668
|
+
[
|
|
7669
|
+
"unchanged",
|
|
7670
|
+
documents2 - n("create") - n("convert") - n("update") - bodyOnly
|
|
7671
|
+
],
|
|
7672
|
+
["archived", n("close") + n("archive")]
|
|
7673
|
+
].filter(([, k]) => k > 0).map(([word, k]) => `${k} ${word}`);
|
|
7674
|
+
ui.line(
|
|
7675
|
+
"synced",
|
|
7676
|
+
`${plural(documents2)}${parts.length > 0 ? `: ${parts.join(", ")}` : ""}`
|
|
7677
|
+
);
|
|
7678
|
+
}
|
|
6899
7679
|
async function syncBoard(c, board, ui, sayLayout) {
|
|
7680
|
+
await fromGithub(c.provider.checkIssues(c.org));
|
|
6900
7681
|
const tabs = await statusTabs(c.rnessDir);
|
|
6901
7682
|
const want = desiredOf(tabs, c.org);
|
|
6902
7683
|
sayLayout(
|
|
6903
7684
|
ui,
|
|
6904
7685
|
await fromGithub(c.provider.ensureLayout(board, layoutOf(tabs)))
|
|
6905
7686
|
);
|
|
6906
|
-
const
|
|
6907
|
-
|
|
6908
|
-
const
|
|
6909
|
-
const
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
7687
|
+
const have = await fromGithub(c.provider.items(board));
|
|
7688
|
+
const record = await openedFile(c.rnessDir);
|
|
7689
|
+
const opened = await readOpened(record);
|
|
7690
|
+
const seen = await seenPaths(c.rnessDir, [
|
|
7691
|
+
...unwantedPaths(want, have),
|
|
7692
|
+
...opened.values()
|
|
7693
|
+
]);
|
|
7694
|
+
const steps = planSync(want, have, seen, opened);
|
|
7695
|
+
const changes = steps.filter((s) => s.kind !== "unchanged").length;
|
|
7696
|
+
let writes = [];
|
|
7697
|
+
let made = 0;
|
|
7698
|
+
try {
|
|
7699
|
+
const placed = /* @__PURE__ */ new Map();
|
|
7700
|
+
const created = /* @__PURE__ */ new Map();
|
|
7701
|
+
const closed = /* @__PURE__ */ new Set();
|
|
7702
|
+
try {
|
|
7703
|
+
for (const step2 of steps) {
|
|
7704
|
+
const p = await fromGithub(c.provider.apply(board, step2));
|
|
7705
|
+
if (p !== null && (step2.kind === "create" || step2.kind === "convert"))
|
|
7706
|
+
placed.set(step2.want.path, p);
|
|
7707
|
+
if (p !== null && step2.kind === "create") created.set(step2.want.path, p);
|
|
7708
|
+
if (step2.kind === "close" || step2.kind === "archive")
|
|
7709
|
+
closed.add(step2.id);
|
|
7710
|
+
if (step2.kind !== "unchanged") made++;
|
|
7711
|
+
}
|
|
7712
|
+
} finally {
|
|
7713
|
+
await writeOpened(
|
|
7714
|
+
record,
|
|
7715
|
+
stillOpened(opened, have, seen, closed, created)
|
|
7716
|
+
);
|
|
7717
|
+
}
|
|
7718
|
+
const issued = issuedAfter(steps, have, placed);
|
|
7719
|
+
const numbers = new Map([...issued].map(([path, i]) => [path, i.number]));
|
|
7720
|
+
const files = await filesOf2(c.rnessDir);
|
|
7721
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
7722
|
+
for (const w of want)
|
|
7723
|
+
bodies.set(
|
|
7724
|
+
w.path,
|
|
7725
|
+
issueBody({
|
|
7726
|
+
path: w.path,
|
|
7727
|
+
text: await readFile13(join26(c.rnessDir, ...w.path.split("/")), "utf8"),
|
|
7728
|
+
org: c.org,
|
|
7729
|
+
numbers,
|
|
7730
|
+
files
|
|
7731
|
+
})
|
|
7732
|
+
);
|
|
7733
|
+
writes = planBodies(bodies, issued);
|
|
7734
|
+
for (const step2 of writes) {
|
|
7735
|
+
await fromGithub(c.provider.apply(board, step2));
|
|
7736
|
+
made++;
|
|
7737
|
+
}
|
|
7738
|
+
} catch (e) {
|
|
7739
|
+
if (!(e instanceof RateLimitError)) throw e;
|
|
7740
|
+
const total = changes + writes.length;
|
|
7741
|
+
throw new Error(
|
|
7742
|
+
`${e.message}: ${total - made} of ${total} changes not made \u2014 the next rness pulse sync makes them`,
|
|
7743
|
+
{ cause: e }
|
|
7744
|
+
);
|
|
7745
|
+
}
|
|
7746
|
+
sayDone(ui, c.org, want.length, steps, writes);
|
|
6919
7747
|
}
|
|
6920
7748
|
async function pulseCreateCommand(opts, deps = {}) {
|
|
6921
7749
|
try {
|
|
6922
7750
|
const terminal = deps.terminal ?? defaultTerminal;
|
|
6923
7751
|
const ui = deps.ui ?? await makeUi(terminal);
|
|
6924
|
-
|
|
7752
|
+
const wait = waitSaid(opts, ui);
|
|
7753
|
+
let c = await context2(opts, wait);
|
|
6925
7754
|
if (c.manifest.pulse !== null)
|
|
6926
7755
|
throw new Error(
|
|
6927
7756
|
`already declared: ${boardUrl(c.org, c.manifest.pulse.project)} \u2014 rness pulse sync`
|
|
@@ -6949,10 +7778,14 @@ async function pulseCreateCommand(opts, deps = {}) {
|
|
|
6949
7778
|
{ terminal, ui, ...opts.cwd === void 0 ? {} : { cwd: opts.cwd } }
|
|
6950
7779
|
);
|
|
6951
7780
|
if (code !== 0) return code;
|
|
6952
|
-
c = {
|
|
7781
|
+
c = {
|
|
7782
|
+
...c,
|
|
7783
|
+
provider: await openProvider(c.manifest, providerOptions(opts, wait))
|
|
7784
|
+
};
|
|
6953
7785
|
const still = await missingAccess(c.provider);
|
|
6954
7786
|
if (still !== null) throw new Error(still);
|
|
6955
7787
|
}
|
|
7788
|
+
await fromGithub(c.provider.checkIssues(c.org));
|
|
6956
7789
|
const login = (await c.provider.identity())?.login ?? "unknown";
|
|
6957
7790
|
ui.line(
|
|
6958
7791
|
"checked",
|
|
@@ -6981,7 +7814,7 @@ async function pulseCreateCommand(opts, deps = {}) {
|
|
|
6981
7814
|
async function pulseSyncCommand(opts, deps = {}) {
|
|
6982
7815
|
try {
|
|
6983
7816
|
const ui = deps.ui ?? await makeUi(deps.terminal ?? defaultTerminal);
|
|
6984
|
-
const c = await context2(opts);
|
|
7817
|
+
const c = await context2(opts, waitSaid(opts, ui));
|
|
6985
7818
|
await syncBoard(c, await declaredBoard(c), ui, eachAdded);
|
|
6986
7819
|
return 0;
|
|
6987
7820
|
} catch (e) {
|
|
@@ -6999,17 +7832,37 @@ async function pulseMarkCommand(opts) {
|
|
|
6999
7832
|
async function mark(opts) {
|
|
7000
7833
|
const c = await context2(opts);
|
|
7001
7834
|
const board = await declaredBoard(c);
|
|
7002
|
-
const
|
|
7835
|
+
const key = `${c.org}-${board.number}`;
|
|
7836
|
+
const lock = opts.sleep === void 0 ? {} : { sleep: opts.sleep };
|
|
7837
|
+
const listed = () => fromGithub(c.provider.items(board, { bodies: false }));
|
|
7838
|
+
let items = await listed();
|
|
7003
7839
|
if (opts.end === true) {
|
|
7004
7840
|
const id = opts.session.split(" \xB7 ").pop() ?? opts.session;
|
|
7005
7841
|
const ids2 = items.filter(
|
|
7006
7842
|
(i) => i.session === opts.session || i.session?.endsWith(` \xB7 ${id}`)
|
|
7007
7843
|
).map((i) => i.id);
|
|
7008
7844
|
await fromGithub(c.provider.mark(board, ids2, null));
|
|
7009
|
-
await syncBoard(c, board, plainUi, eachAdded)
|
|
7845
|
+
await oneSyncAtATime(key, () => syncBoard(c, board, plainUi, eachAdded), {
|
|
7846
|
+
...lock,
|
|
7847
|
+
wait: true
|
|
7848
|
+
});
|
|
7010
7849
|
return 0;
|
|
7011
7850
|
}
|
|
7012
7851
|
const wanted = new Set(opts.paths);
|
|
7852
|
+
const documents2 = new Set(
|
|
7853
|
+
(await statusTabs(c.rnessDir)).flatMap((t) => t.rows.map((r) => r.path))
|
|
7854
|
+
);
|
|
7855
|
+
const onBoard = new Set(
|
|
7856
|
+
items.flatMap((i) => i.path === null ? [] : [i.path])
|
|
7857
|
+
);
|
|
7858
|
+
if ([...wanted].some((p) => documents2.has(p) && !onBoard.has(p))) {
|
|
7859
|
+
const synced = await oneSyncAtATime(
|
|
7860
|
+
key,
|
|
7861
|
+
() => syncBoard(c, board, plainUi, eachAdded),
|
|
7862
|
+
lock
|
|
7863
|
+
);
|
|
7864
|
+
if (synced) items = await listed();
|
|
7865
|
+
}
|
|
7013
7866
|
const ids = items.filter((i) => i.path !== null && wanted.has(i.path)).map((i) => i.id);
|
|
7014
7867
|
await fromGithub(c.provider.mark(board, ids, opts.session));
|
|
7015
7868
|
return 0;
|
|
@@ -7528,4 +8381,4 @@ export {
|
|
|
7528
8381
|
OWN_COMMANDS,
|
|
7529
8382
|
catchUp
|
|
7530
8383
|
};
|
|
7531
|
-
//# sourceMappingURL=chunk-
|
|
8384
|
+
//# sourceMappingURL=chunk-4VTYSPZW.js.map
|