@open-tomato/rafa 0.33.0 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/bundled/operators/agents/rafa-stretch-analyst.md +53 -0
- package/dist/bundled/operators/agents/rafa-stretch-engineer.md +15 -5
- package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +8 -2
- package/dist/bundled/skills/dev-planner/SKILL.md +3 -3
- package/dist/bundled/skills/git-workflow/SKILL.md +1 -1
- package/dist/cli.js +1935 -605
- package/dist/effort/store/index.js +2 -2
- package/dist/{index-kz7e4gtt.js → index-bebv92ha.js} +112 -58
- package/dist/{index-13caphhp.js → index-nr4hdb35.js} +672 -23
- package/dist/{index-w3vyb08t.js → index-s8ff86xt.js} +689 -198
- package/dist/{index-zn08ywsv.js → index-v8d7c6sw.js} +1 -1
- package/dist/{index-dv0ycmp8.js → index-vp0xghk2.js} +40 -0
- package/dist/index.js +4 -4
- package/dist/plan/index.js +2 -2
- package/package.json +4 -2
|
@@ -321,6 +321,7 @@ import {
|
|
|
321
321
|
sessionLine,
|
|
322
322
|
sessionLogDir,
|
|
323
323
|
setUpBoard,
|
|
324
|
+
setUpLabels,
|
|
324
325
|
severalRoadmapsMessage,
|
|
325
326
|
shellQuote,
|
|
326
327
|
show_default,
|
|
@@ -356,7 +357,7 @@ import {
|
|
|
356
357
|
worktreeAt,
|
|
357
358
|
writeSession,
|
|
358
359
|
writeShippedFile
|
|
359
|
-
} from "./index-
|
|
360
|
+
} from "./index-s8ff86xt.js";
|
|
360
361
|
import {
|
|
361
362
|
PROBE_TIMEOUT_MS,
|
|
362
363
|
loadPlanPrerequisites,
|
|
@@ -368,7 +369,7 @@ import {
|
|
|
368
369
|
runPreflight,
|
|
369
370
|
splitBlockerComment,
|
|
370
371
|
trackerPathFor
|
|
371
|
-
} from "./index-
|
|
372
|
+
} from "./index-v8d7c6sw.js";
|
|
372
373
|
import {
|
|
373
374
|
ACTION_HEADING,
|
|
374
375
|
CAUSE_HEADING,
|
|
@@ -388,6 +389,7 @@ import {
|
|
|
388
389
|
MOVE_TO_SQLITE,
|
|
389
390
|
MergeRefusal,
|
|
390
391
|
POSITION_FILE,
|
|
392
|
+
RAFA_VERSION,
|
|
391
393
|
RebuildRefusal,
|
|
392
394
|
SCOPE_DIR,
|
|
393
395
|
SKILL_FILE,
|
|
@@ -421,6 +423,7 @@ import {
|
|
|
421
423
|
isFile,
|
|
422
424
|
issueLine,
|
|
423
425
|
loadConfig,
|
|
426
|
+
mainCheckoutOf,
|
|
424
427
|
mergeFrontmatter,
|
|
425
428
|
mergeStore,
|
|
426
429
|
migrateStore,
|
|
@@ -461,7 +464,7 @@ import {
|
|
|
461
464
|
versionLine,
|
|
462
465
|
writeFrontmatter,
|
|
463
466
|
writePositionFile
|
|
464
|
-
} from "./index-
|
|
467
|
+
} from "./index-bebv92ha.js";
|
|
465
468
|
import {
|
|
466
469
|
CONFIG_DEFAULTS,
|
|
467
470
|
CONFIG_FILE,
|
|
@@ -476,7 +479,7 @@ import {
|
|
|
476
479
|
readItemBytes,
|
|
477
480
|
readRafaBlocks,
|
|
478
481
|
resolveTiers
|
|
479
|
-
} from "./index-
|
|
482
|
+
} from "./index-vp0xghk2.js";
|
|
480
483
|
import {
|
|
481
484
|
promotable
|
|
482
485
|
} from "./index-d6d0942q.js";
|
|
@@ -10584,6 +10587,9 @@ function bugLine(criterion, bug) {
|
|
|
10584
10587
|
case "commented": {
|
|
10585
10588
|
return { text: `${which}: recurs in ${bug.ref === null ? "an issue" : issueName2(bug.ref)} on the ${bug.channel} tracker; commented on it.`, warn: false };
|
|
10586
10589
|
}
|
|
10590
|
+
case "inherited": {
|
|
10591
|
+
return { text: `${which}: not filed: a test failure the run started with.`, warn: false };
|
|
10592
|
+
}
|
|
10587
10593
|
case "skipped": {
|
|
10588
10594
|
return { text: `${which}: not filed: ${bug.problem ?? "no reason given"}.`, warn: true };
|
|
10589
10595
|
}
|
|
@@ -10621,7 +10627,8 @@ async function fileFailures(run, epic, failures, commit) {
|
|
|
10621
10627
|
tracker,
|
|
10622
10628
|
privateTracker: run.seams.privateTracker,
|
|
10623
10629
|
secrets,
|
|
10624
|
-
seams: run.seams.findings
|
|
10630
|
+
seams: run.seams.findings,
|
|
10631
|
+
similarity: run.config
|
|
10625
10632
|
});
|
|
10626
10633
|
for (const bug of result2.bugs) {
|
|
10627
10634
|
const line = bugLine(criterion, bug);
|
|
@@ -11682,6 +11689,10 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
11682
11689
|
"# claims:",
|
|
11683
11690
|
`# staleAfter: ${CONFIG_DEFAULTS.claimsStaleAfter} # 36h | 3d | disabled; how long a rafa:claimed claim stands before a takeover`,
|
|
11684
11691
|
`# ahead: ${CONFIG_DEFAULTS.claimsAhead} # off | allow; whether a claim may reach one issue ahead`,
|
|
11692
|
+
"# triage:",
|
|
11693
|
+
"# similarity:",
|
|
11694
|
+
`# threshold: ${String(CONFIG_DEFAULTS.triageSimilarityThreshold)} # above 0 and at most 1 | false; the least score at which triage comments on the nearest open bug`,
|
|
11695
|
+
`# candidates: ${String(CONFIG_DEFAULTS.triageSimilarityCandidates)} # 1..10, the nearest issues a new bug lists as Possible duplicates`,
|
|
11685
11696
|
"# release:",
|
|
11686
11697
|
`# enabled: ${String(CONFIG_DEFAULTS.releaseEnabled)} # true | false | auto, which is on when both files below exist`,
|
|
11687
11698
|
`# versionFile: ${CONFIG_DEFAULTS.releaseVersionFile}`,
|
|
@@ -15209,8 +15220,16 @@ async function guardBeforeMerge(step) {
|
|
|
15209
15220
|
var NO_WORKFLOW_WARNING = "nothing on GitHub has tested this branch; you are relying on the checks run locally";
|
|
15210
15221
|
var WORKFLOWS_EXIST_WARNING = "CI may not have started (a path filter, a draft, Actions disabled, or it has not registered yet); this is probably not what you want";
|
|
15211
15222
|
var UNCHECKED_MERGE_SENTENCE = "Merged with no checks reported, by rafa pr merge --skip-checks.";
|
|
15212
|
-
function uncheckedCaseOf(workflowCount) {
|
|
15213
|
-
|
|
15223
|
+
function uncheckedCaseOf(workflowCount, baseWorkflows = null) {
|
|
15224
|
+
if (workflowCount === 0)
|
|
15225
|
+
return "no-workflow";
|
|
15226
|
+
if (workflowCount !== null && baseWorkflows !== null && !baseWorkflows.runsOnPullRequests) {
|
|
15227
|
+
return "no-pull-request-workflow";
|
|
15228
|
+
}
|
|
15229
|
+
return "workflows-exist";
|
|
15230
|
+
}
|
|
15231
|
+
function noPullRequestWorkflowLine(base) {
|
|
15232
|
+
return `no workflow runs on pull requests into ${base}`;
|
|
15214
15233
|
}
|
|
15215
15234
|
function workflowCountLine(workflowCount) {
|
|
15216
15235
|
if (workflowCount === null)
|
|
@@ -15224,26 +15243,181 @@ function skipChecksCommand(number) {
|
|
|
15224
15243
|
function uncheckedQuestion(number) {
|
|
15225
15244
|
return `Merge #${number} with no checks? [y/N]`;
|
|
15226
15245
|
}
|
|
15227
|
-
function uncheckedComment(workflowCount) {
|
|
15228
|
-
|
|
15246
|
+
function uncheckedComment(workflowCount, baseLine = null) {
|
|
15247
|
+
const countLine2 = workflowCountLine(workflowCount);
|
|
15248
|
+
const paragraphs = baseLine === null ? [UNCHECKED_MERGE_SENTENCE, countLine2] : [UNCHECKED_MERGE_SENTENCE, countLine2, baseLine];
|
|
15249
|
+
return paragraphs.join(`
|
|
15229
15250
|
|
|
15230
|
-
|
|
15251
|
+
`);
|
|
15231
15252
|
}
|
|
15232
|
-
function
|
|
15233
|
-
|
|
15234
|
-
|
|
15253
|
+
function warningOf(which, baseWorkflows) {
|
|
15254
|
+
if (which === "no-workflow")
|
|
15255
|
+
return NO_WORKFLOW_WARNING;
|
|
15256
|
+
if (which === "no-pull-request-workflow" && baseWorkflows !== null)
|
|
15257
|
+
return noPullRequestWorkflowLine(baseWorkflows.base);
|
|
15258
|
+
return WORKFLOWS_EXIST_WARNING;
|
|
15259
|
+
}
|
|
15260
|
+
function readUnchecked(number, workflowCount, baseWorkflows = null) {
|
|
15261
|
+
const which = uncheckedCaseOf(workflowCount, baseWorkflows);
|
|
15262
|
+
const warning = warningOf(which, baseWorkflows);
|
|
15263
|
+
const isBaseCase = which === "no-pull-request-workflow";
|
|
15235
15264
|
return Object.freeze({
|
|
15236
15265
|
case: which,
|
|
15237
15266
|
warning: Object.freeze([workflowCountLine(workflowCount), warning]),
|
|
15238
|
-
yesMayAnswer: which
|
|
15267
|
+
yesMayAnswer: which !== "workflows-exist",
|
|
15239
15268
|
question: uncheckedQuestion(number),
|
|
15240
|
-
comment: uncheckedComment(workflowCount)
|
|
15269
|
+
comment: uncheckedComment(workflowCount, isBaseCase ? warning : null)
|
|
15241
15270
|
});
|
|
15242
15271
|
}
|
|
15243
15272
|
|
|
15273
|
+
// src/pr/workflow-triggers.ts
|
|
15274
|
+
var PULL_REQUEST_EVENT = "pull_request";
|
|
15275
|
+
var UNKNOWN = "unknown";
|
|
15276
|
+
var BRACKET_BODY = /^[A-Za-z0-9-]+$/;
|
|
15277
|
+
var REGEX_SPECIAL = /[.*+?^${}()|[\]\\/]/g;
|
|
15278
|
+
function workflowsRunOnPullRequestsInto(workflowTexts, base) {
|
|
15279
|
+
return workflowTexts.some((text) => workflowNamesBase(text, base));
|
|
15280
|
+
}
|
|
15281
|
+
function workflowNamesBase(workflowText, base) {
|
|
15282
|
+
let document;
|
|
15283
|
+
try {
|
|
15284
|
+
document = Bun.YAML.parse(workflowText);
|
|
15285
|
+
} catch {
|
|
15286
|
+
return true;
|
|
15287
|
+
}
|
|
15288
|
+
return readWorkflow(document, base) !== false;
|
|
15289
|
+
}
|
|
15290
|
+
function readWorkflow(document, base) {
|
|
15291
|
+
if (!isPlainObject(document) || !("on" in document))
|
|
15292
|
+
return UNKNOWN;
|
|
15293
|
+
const on = document.on;
|
|
15294
|
+
if (typeof on === "string")
|
|
15295
|
+
return on === PULL_REQUEST_EVENT;
|
|
15296
|
+
if (Array.isArray(on)) {
|
|
15297
|
+
if (!on.every((event) => typeof event === "string"))
|
|
15298
|
+
return UNKNOWN;
|
|
15299
|
+
return on.includes(PULL_REQUEST_EVENT);
|
|
15300
|
+
}
|
|
15301
|
+
if (!isPlainObject(on))
|
|
15302
|
+
return UNKNOWN;
|
|
15303
|
+
if (!(PULL_REQUEST_EVENT in on))
|
|
15304
|
+
return false;
|
|
15305
|
+
return readPullRequestTrigger(on[PULL_REQUEST_EVENT], base);
|
|
15306
|
+
}
|
|
15307
|
+
function readPullRequestTrigger(trigger, base) {
|
|
15308
|
+
if (trigger === null || trigger === undefined)
|
|
15309
|
+
return true;
|
|
15310
|
+
if (!isPlainObject(trigger))
|
|
15311
|
+
return UNKNOWN;
|
|
15312
|
+
const branches = trigger.branches;
|
|
15313
|
+
const ignored = trigger["branches-ignore"];
|
|
15314
|
+
if (branches !== undefined && ignored !== undefined)
|
|
15315
|
+
return UNKNOWN;
|
|
15316
|
+
if (branches !== undefined)
|
|
15317
|
+
return readBranches(branches, base);
|
|
15318
|
+
if (ignored !== undefined)
|
|
15319
|
+
return readBranchesIgnore(ignored, base);
|
|
15320
|
+
return true;
|
|
15321
|
+
}
|
|
15322
|
+
function readBranches(filter, base) {
|
|
15323
|
+
const patterns = patternListOf(filter);
|
|
15324
|
+
if (patterns === null)
|
|
15325
|
+
return UNKNOWN;
|
|
15326
|
+
let named2 = false;
|
|
15327
|
+
for (const pattern of patterns) {
|
|
15328
|
+
const negated = pattern.startsWith("!");
|
|
15329
|
+
const matches = branchPatternMatches(negated ? pattern.slice(1) : pattern, base);
|
|
15330
|
+
if (matches === null)
|
|
15331
|
+
return UNKNOWN;
|
|
15332
|
+
if (matches)
|
|
15333
|
+
named2 = !negated;
|
|
15334
|
+
}
|
|
15335
|
+
return named2;
|
|
15336
|
+
}
|
|
15337
|
+
function readBranchesIgnore(filter, base) {
|
|
15338
|
+
const patterns = patternListOf(filter);
|
|
15339
|
+
if (patterns === null || patterns.some((pattern) => pattern.startsWith("!")))
|
|
15340
|
+
return UNKNOWN;
|
|
15341
|
+
let anyMatch = false;
|
|
15342
|
+
for (const pattern of patterns) {
|
|
15343
|
+
const matches = branchPatternMatches(pattern, base);
|
|
15344
|
+
if (matches === null)
|
|
15345
|
+
return UNKNOWN;
|
|
15346
|
+
anyMatch = anyMatch || matches;
|
|
15347
|
+
}
|
|
15348
|
+
return !anyMatch;
|
|
15349
|
+
}
|
|
15350
|
+
function patternListOf(filter) {
|
|
15351
|
+
if (typeof filter === "string")
|
|
15352
|
+
return [filter];
|
|
15353
|
+
if (Array.isArray(filter) && filter.every((pattern) => typeof pattern === "string"))
|
|
15354
|
+
return filter;
|
|
15355
|
+
return null;
|
|
15356
|
+
}
|
|
15357
|
+
function branchPatternMatches(pattern, branch) {
|
|
15358
|
+
const source = regexSourceOf(pattern);
|
|
15359
|
+
if (source === null)
|
|
15360
|
+
return null;
|
|
15361
|
+
try {
|
|
15362
|
+
return new RegExp(`^${source}$`).test(branch);
|
|
15363
|
+
} catch {
|
|
15364
|
+
return null;
|
|
15365
|
+
}
|
|
15366
|
+
}
|
|
15367
|
+
function regexSourceOf(pattern) {
|
|
15368
|
+
let source = "";
|
|
15369
|
+
let quantifiable = false;
|
|
15370
|
+
let index = 0;
|
|
15371
|
+
while (index < pattern.length) {
|
|
15372
|
+
const char = pattern.charAt(index);
|
|
15373
|
+
if (char === "*") {
|
|
15374
|
+
const isDouble = pattern.charAt(index + 1) === "*";
|
|
15375
|
+
source += isDouble ? ".*" : "[^/]*";
|
|
15376
|
+
index += isDouble ? 2 : 1;
|
|
15377
|
+
quantifiable = false;
|
|
15378
|
+
} else if (char === "?" || char === "+") {
|
|
15379
|
+
if (!quantifiable)
|
|
15380
|
+
return null;
|
|
15381
|
+
source += char;
|
|
15382
|
+
index += 1;
|
|
15383
|
+
quantifiable = false;
|
|
15384
|
+
} else if (char === "[") {
|
|
15385
|
+
const close = pattern.indexOf("]", index + 1);
|
|
15386
|
+
if (close === -1)
|
|
15387
|
+
return null;
|
|
15388
|
+
const body = pattern.slice(index + 1, close);
|
|
15389
|
+
if (!BRACKET_BODY.test(body))
|
|
15390
|
+
return null;
|
|
15391
|
+
source += `[${body}]`;
|
|
15392
|
+
index = close + 1;
|
|
15393
|
+
quantifiable = true;
|
|
15394
|
+
} else if (char === "\\") {
|
|
15395
|
+
if (index + 1 >= pattern.length)
|
|
15396
|
+
return null;
|
|
15397
|
+
source += escapeRegex(pattern.charAt(index + 1));
|
|
15398
|
+
index += 2;
|
|
15399
|
+
quantifiable = true;
|
|
15400
|
+
} else {
|
|
15401
|
+
source += escapeRegex(char);
|
|
15402
|
+
index += 1;
|
|
15403
|
+
quantifiable = true;
|
|
15404
|
+
}
|
|
15405
|
+
}
|
|
15406
|
+
return source;
|
|
15407
|
+
}
|
|
15408
|
+
function escapeRegex(char) {
|
|
15409
|
+
return char.replace(REGEX_SPECIAL, "\\$&");
|
|
15410
|
+
}
|
|
15411
|
+
function isPlainObject(value) {
|
|
15412
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15413
|
+
}
|
|
15414
|
+
|
|
15244
15415
|
// src/commands/pr/merge-unchecked.ts
|
|
15245
15416
|
var INDENT4 = " ";
|
|
15246
15417
|
var YES_ANSWERS3 = ["y", "yes"];
|
|
15418
|
+
var WORKFLOWS_DIR = ".github/workflows/";
|
|
15419
|
+
var WORKFLOW_ENDINGS = [".yml", ".yaml"];
|
|
15420
|
+
var LS_TREE_ENTRY = /^\d+ (\w+) ([0-9a-f]+)\t(.+)$/s;
|
|
15247
15421
|
function refusal10(head, reading, summary, tail) {
|
|
15248
15422
|
return new CommandExit(1, [
|
|
15249
15423
|
`\u274C ${head}`,
|
|
@@ -15260,10 +15434,37 @@ async function workflowCountOf(pulls) {
|
|
|
15260
15434
|
return null;
|
|
15261
15435
|
}
|
|
15262
15436
|
}
|
|
15437
|
+
function workflowBlobsOf(listing) {
|
|
15438
|
+
return listing.split("\x00").map((entry) => LS_TREE_ENTRY.exec(entry)).filter((match) => match !== null).filter(([, type, , path]) => type === "blob" && WORKFLOW_ENDINGS.some((ending) => path?.endsWith(ending) === true)).map(([, , oid]) => oid ?? "");
|
|
15439
|
+
}
|
|
15440
|
+
function readBaseWorkflows(git, base) {
|
|
15441
|
+
if (base === "" || base.startsWith("-"))
|
|
15442
|
+
return null;
|
|
15443
|
+
if (!git(["fetch", "origin", base]).ok)
|
|
15444
|
+
return null;
|
|
15445
|
+
const listed2 = git(["ls-tree", "--full-tree", "-z", `origin/${base}`, WORKFLOWS_DIR]);
|
|
15446
|
+
if (!listed2.ok)
|
|
15447
|
+
return null;
|
|
15448
|
+
const blobs = workflowBlobsOf(listed2.stdout);
|
|
15449
|
+
if (blobs.length === 0)
|
|
15450
|
+
return null;
|
|
15451
|
+
const shown = blobs.map((oid) => git(["cat-file", "blob", oid]));
|
|
15452
|
+
if (shown.some((result2) => !result2.ok))
|
|
15453
|
+
return null;
|
|
15454
|
+
const texts = shown.map((result2) => result2.stdout);
|
|
15455
|
+
return Object.freeze({ base, runsOnPullRequests: workflowsRunOnPullRequestsInto(texts, base) });
|
|
15456
|
+
}
|
|
15457
|
+
function baseWorkflowsOf(options, workflowCount) {
|
|
15458
|
+
const { git, base } = options;
|
|
15459
|
+
if (workflowCount === null || workflowCount === 0 || git === undefined || base === undefined)
|
|
15460
|
+
return null;
|
|
15461
|
+
return readBaseWorkflows(git, base);
|
|
15462
|
+
}
|
|
15263
15463
|
async function readUncheckedMerge(options) {
|
|
15264
15464
|
const { pulls, number, yes, summary } = options;
|
|
15265
15465
|
const workflowCount = await workflowCountOf(pulls);
|
|
15266
|
-
const
|
|
15466
|
+
const baseWorkflows = baseWorkflowsOf(options, workflowCount);
|
|
15467
|
+
const reading = readUnchecked(number, workflowCount, baseWorkflows);
|
|
15267
15468
|
if (yes && !reading.yesMayAnswer) {
|
|
15268
15469
|
throw refusal10(`rafa pr merge refuses --yes for #${number} with no checks: workflows exist, or their count could not be read.`, reading, summary, "That is more likely a fault than a choice, so a person has to answer. Run it again without --yes on a terminal.");
|
|
15269
15470
|
}
|
|
@@ -15272,7 +15473,7 @@ async function readUncheckedMerge(options) {
|
|
|
15272
15473
|
const tail = reading.yesMayAnswer ? "Merge it without the question with --yes." : "--yes is refused here, so run it on a terminal and answer it.";
|
|
15273
15474
|
throw refusal10(`rafa pr merge asks before merging #${number} with no checks, and standard input is no terminal.`, reading, summary, tail);
|
|
15274
15475
|
}
|
|
15275
|
-
return Object.freeze({ workflowCount, reading, yes });
|
|
15476
|
+
return Object.freeze({ workflowCount, baseWorkflows, reading, yes });
|
|
15276
15477
|
}
|
|
15277
15478
|
function isYes(answer2) {
|
|
15278
15479
|
return answer2 !== null && YES_ANSWERS3.includes(answer2.trim().toLowerCase());
|
|
@@ -15454,8 +15655,8 @@ async function reportAfterCleanUp(context, pr, seams, detail) {
|
|
|
15454
15655
|
return { unblocked: null, freed };
|
|
15455
15656
|
}
|
|
15456
15657
|
async function askToMerge(context, pr, seams, ask) {
|
|
15457
|
-
const { number, summary, yes, skipChecks } = ask;
|
|
15458
|
-
const unchecked = skipChecks ? await readUncheckedMerge({ pulls: pr.pulls, number, yes, summary, isTerminal: seams.isTerminal }) : null;
|
|
15658
|
+
const { number, summary, yes, skipChecks, base, git } = ask;
|
|
15659
|
+
const unchecked = skipChecks ? await readUncheckedMerge({ pulls: pr.pulls, number, yes, summary, isTerminal: seams.isTerminal, base, git }) : null;
|
|
15459
15660
|
if (unchecked === null && !yes)
|
|
15460
15661
|
requireTerminal(seams, summary);
|
|
15461
15662
|
context.output.info(summary);
|
|
@@ -15520,7 +15721,7 @@ async function runMerge2(context, seams) {
|
|
|
15520
15721
|
openPrompter: prompterOf(seams)
|
|
15521
15722
|
});
|
|
15522
15723
|
const summary = summaryLine2(detail, method);
|
|
15523
|
-
const answer2 = guard.go ? await askToMerge(context, pr, seams, { number: detail.number, summary, yes, skipChecks }) : { go: false, unchecked: null };
|
|
15724
|
+
const answer2 = guard.go ? await askToMerge(context, pr, seams, { number: detail.number, summary, yes, skipChecks, base: detail.baseRefName, git }) : { go: false, unchecked: null };
|
|
15524
15725
|
const answered3 = {
|
|
15525
15726
|
number: detail.number,
|
|
15526
15727
|
source: pick.source,
|
|
@@ -15584,7 +15785,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
|
|
|
15584
15785
|
subject: "pr",
|
|
15585
15786
|
action: "merge",
|
|
15586
15787
|
summary: "merge a pull request and clean up both branches",
|
|
15587
|
-
description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
|
|
15788
|
+
description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow, or" + " where no workflow file on the base has a `pull_request` trigger naming it)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
|
|
15588
15789
|
args: [
|
|
15589
15790
|
{
|
|
15590
15791
|
name: "n",
|
|
@@ -15600,7 +15801,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
|
|
|
15600
15801
|
},
|
|
15601
15802
|
{
|
|
15602
15803
|
name: "skip-checks",
|
|
15603
|
-
description: "Merge a pull request that reports no checks at all, after a warning and its own question." + " Refused where the pull request reports any check; `--yes` beside it is refused where the repository" + " defines a workflow or its workflow count could not be read.",
|
|
15804
|
+
description: "Merge a pull request that reports no checks at all, after a warning and its own question." + " Refused where the pull request reports any check; `--yes` beside it is refused where the repository" + " defines a workflow that may run on pull requests into the base, or its workflow count could not be read.",
|
|
15604
15805
|
type: "boolean"
|
|
15605
15806
|
},
|
|
15606
15807
|
{
|
|
@@ -21880,6 +22081,445 @@ function createStatusCommand(seams = DEFAULT_STATUS_SEAMS) {
|
|
|
21880
22081
|
}
|
|
21881
22082
|
var status_default3 = createStatusCommand();
|
|
21882
22083
|
|
|
22084
|
+
// src/commands/update/stub.ts
|
|
22085
|
+
var ISSUES_URL = "https://github.com/open-tomato/rafa/issues";
|
|
22086
|
+
function stubMessage(action, issue2) {
|
|
22087
|
+
return `rafa update ${action} is a feature in development, tracked in ${ISSUES_URL}/${String(issue2)}.` + " Nothing was changed.";
|
|
22088
|
+
}
|
|
22089
|
+
function createUpdateStub(spec) {
|
|
22090
|
+
const command = {
|
|
22091
|
+
name: `update ${spec.action}`,
|
|
22092
|
+
subject: "update",
|
|
22093
|
+
action: spec.action,
|
|
22094
|
+
summary: `${spec.summary} (in development, #${String(spec.issue)})`,
|
|
22095
|
+
description: `${spec.summary}. In development: it refuses with exit code 1, naming #${String(spec.issue)},` + " and changes nothing.",
|
|
22096
|
+
args: [],
|
|
22097
|
+
flags: [],
|
|
22098
|
+
examples: [{ cmd: `rafa update ${spec.action}`, note: `Refuses, naming #${String(spec.issue)}; changes nothing.` }],
|
|
22099
|
+
outputs: ["text", "json"],
|
|
22100
|
+
needsProject: false,
|
|
22101
|
+
...spec.hidden === true ? { hidden: true } : {},
|
|
22102
|
+
run: () => Promise.reject(new CommandExit(1, stubMessage(spec.action, spec.issue)))
|
|
22103
|
+
};
|
|
22104
|
+
return Object.freeze(command);
|
|
22105
|
+
}
|
|
22106
|
+
|
|
22107
|
+
// src/commands/update/board.ts
|
|
22108
|
+
var board_default = createUpdateStub({ action: "board", summary: "apply the board's newest setup and its migrations", issue: 717 });
|
|
22109
|
+
|
|
22110
|
+
// src/project/lock.ts
|
|
22111
|
+
import { existsSync as existsSync15, lstatSync as lstatSync2, readFileSync as readFileSync28, writeFileSync as writeFileSync12 } from "fs";
|
|
22112
|
+
import { join as join40 } from "path";
|
|
22113
|
+
var LOCK_FILE = "rafa.lock";
|
|
22114
|
+
var LOCKFILE_VERSION = 1;
|
|
22115
|
+
|
|
22116
|
+
class ProjectLockError extends Error {
|
|
22117
|
+
name = "ProjectLockError";
|
|
22118
|
+
}
|
|
22119
|
+
function lockPath(root) {
|
|
22120
|
+
return join40(root, LOCK_FILE);
|
|
22121
|
+
}
|
|
22122
|
+
function projectLockText(version2) {
|
|
22123
|
+
const lock = { lockfileVersion: LOCKFILE_VERSION, rafa: version2 };
|
|
22124
|
+
return `${JSON.stringify(lock, null, 2)}
|
|
22125
|
+
`;
|
|
22126
|
+
}
|
|
22127
|
+
function isLink(path) {
|
|
22128
|
+
try {
|
|
22129
|
+
return lstatSync2(path).isSymbolicLink();
|
|
22130
|
+
} catch {
|
|
22131
|
+
return false;
|
|
22132
|
+
}
|
|
22133
|
+
}
|
|
22134
|
+
function refuse8(problem2) {
|
|
22135
|
+
throw new ProjectLockError(`${LOCK_FILE} ${problem2}`);
|
|
22136
|
+
}
|
|
22137
|
+
function lockOf(value) {
|
|
22138
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
22139
|
+
refuse8("holds no JSON object");
|
|
22140
|
+
const record = value;
|
|
22141
|
+
const format = record["lockfileVersion"];
|
|
22142
|
+
if (format !== LOCKFILE_VERSION) {
|
|
22143
|
+
refuse8(`is in lockfileVersion ${String(format)}, and this rafa reads ${String(LOCKFILE_VERSION)}: a newer rafa wrote it`);
|
|
22144
|
+
}
|
|
22145
|
+
const rafa = record["rafa"];
|
|
22146
|
+
if (typeof rafa !== "string" || parseSemanticVersion(rafa) === null) {
|
|
22147
|
+
refuse8(`names rafa ${JSON.stringify(rafa)}, which is no version`);
|
|
22148
|
+
}
|
|
22149
|
+
return { lockfileVersion: LOCKFILE_VERSION, rafa };
|
|
22150
|
+
}
|
|
22151
|
+
function readProjectLock(root) {
|
|
22152
|
+
const path = lockPath(root);
|
|
22153
|
+
if (!existsSync15(path)) {
|
|
22154
|
+
if (isLink(path))
|
|
22155
|
+
refuse8("is a link to nothing");
|
|
22156
|
+
return null;
|
|
22157
|
+
}
|
|
22158
|
+
let text;
|
|
22159
|
+
try {
|
|
22160
|
+
text = readFileSync28(path, "utf8");
|
|
22161
|
+
} catch (error) {
|
|
22162
|
+
refuse8(`could not be read: ${messageOf(error)}`);
|
|
22163
|
+
}
|
|
22164
|
+
let value;
|
|
22165
|
+
try {
|
|
22166
|
+
value = JSON.parse(text);
|
|
22167
|
+
} catch {
|
|
22168
|
+
refuse8("is not JSON");
|
|
22169
|
+
}
|
|
22170
|
+
return lockOf(value);
|
|
22171
|
+
}
|
|
22172
|
+
function writeProjectLock(root, version2) {
|
|
22173
|
+
writeFileSync12(lockPath(root), projectLockText(version2), "utf8");
|
|
22174
|
+
}
|
|
22175
|
+
|
|
22176
|
+
// src/project/update-current.ts
|
|
22177
|
+
import { existsSync as existsSync16 } from "fs";
|
|
22178
|
+
import { join as join41 } from "path";
|
|
22179
|
+
var DEPRECATIONS = Object.freeze([]);
|
|
22180
|
+
function missingFolders(root) {
|
|
22181
|
+
return PROJECT_TREE.map((name) => join41(SCOPE_DIR, name)).filter((relative9) => !existsSync16(join41(root, relative9)));
|
|
22182
|
+
}
|
|
22183
|
+
async function readBoard3(access) {
|
|
22184
|
+
if (access.gh === null)
|
|
22185
|
+
return { kind: "skipped", reason: access.reason };
|
|
22186
|
+
try {
|
|
22187
|
+
const held = await listBoardLabels(access.gh);
|
|
22188
|
+
return { kind: "labels", missing: missingBoardLabels(held).map((label) => label.name) };
|
|
22189
|
+
} catch (error) {
|
|
22190
|
+
return { kind: "skipped", reason: messageOf(error) };
|
|
22191
|
+
}
|
|
22192
|
+
}
|
|
22193
|
+
function lockChangeOf(range) {
|
|
22194
|
+
if (range.kind === "adopt")
|
|
22195
|
+
return "created";
|
|
22196
|
+
return range.kind === "patch" ? "updated" : "unchanged";
|
|
22197
|
+
}
|
|
22198
|
+
function fromOf(range) {
|
|
22199
|
+
if (range.kind === "patch")
|
|
22200
|
+
return range.from;
|
|
22201
|
+
if (range.kind === "same")
|
|
22202
|
+
return range.to;
|
|
22203
|
+
return null;
|
|
22204
|
+
}
|
|
22205
|
+
async function readUpdatePlan(roots, range, board) {
|
|
22206
|
+
const { root, lockRoot } = roots;
|
|
22207
|
+
return {
|
|
22208
|
+
root,
|
|
22209
|
+
lockRoot,
|
|
22210
|
+
from: fromOf(range),
|
|
22211
|
+
to: range.to,
|
|
22212
|
+
folders: missingFolders(root),
|
|
22213
|
+
board: await readBoard3(board),
|
|
22214
|
+
deprecations: DEPRECATIONS,
|
|
22215
|
+
lock: lockChangeOf(range)
|
|
22216
|
+
};
|
|
22217
|
+
}
|
|
22218
|
+
function planChanges(plan2) {
|
|
22219
|
+
const labels = plan2.board.kind === "labels" ? plan2.board.missing.length : 0;
|
|
22220
|
+
return plan2.folders.length > 0 || labels > 0 || plan2.deprecations.length > 0 || plan2.lock !== "unchanged";
|
|
22221
|
+
}
|
|
22222
|
+
|
|
22223
|
+
class UpdateStepError extends Error {
|
|
22224
|
+
step;
|
|
22225
|
+
written;
|
|
22226
|
+
name = "UpdateStepError";
|
|
22227
|
+
constructor(step, message, written) {
|
|
22228
|
+
super(message);
|
|
22229
|
+
this.step = step;
|
|
22230
|
+
this.written = written;
|
|
22231
|
+
}
|
|
22232
|
+
}
|
|
22233
|
+
function foldersNowThere(plan2) {
|
|
22234
|
+
return plan2.folders.filter((relative9) => existsSync16(join41(plan2.root, relative9)));
|
|
22235
|
+
}
|
|
22236
|
+
function writeFolders(plan2) {
|
|
22237
|
+
if (plan2.folders.length === 0)
|
|
22238
|
+
return [];
|
|
22239
|
+
try {
|
|
22240
|
+
return writeProjectScope(plan2.root).filter((write) => write.change === "created").map((write) => write.path);
|
|
22241
|
+
} catch (error) {
|
|
22242
|
+
throw new UpdateStepError("folders", messageOf(error), foldersNowThere(plan2));
|
|
22243
|
+
}
|
|
22244
|
+
}
|
|
22245
|
+
function createdLabels(parts) {
|
|
22246
|
+
return parts.filter((part) => part.outcome === "created").map((part) => `label ${part.name}`);
|
|
22247
|
+
}
|
|
22248
|
+
async function applyUpdatePlan(plan2, gh) {
|
|
22249
|
+
const folders = writeFolders(plan2);
|
|
22250
|
+
const labels = gh !== null && plan2.board.kind === "labels" && plan2.board.missing.length > 0 ? await setUpLabels(gh) : [];
|
|
22251
|
+
if (plan2.lock !== "unchanged") {
|
|
22252
|
+
try {
|
|
22253
|
+
writeProjectLock(plan2.lockRoot, plan2.to);
|
|
22254
|
+
} catch (error) {
|
|
22255
|
+
throw new UpdateStepError("lock", messageOf(error), [...folders, ...createdLabels(labels)]);
|
|
22256
|
+
}
|
|
22257
|
+
}
|
|
22258
|
+
return { folders, labels, lock: plan2.lock };
|
|
22259
|
+
}
|
|
22260
|
+
|
|
22261
|
+
// src/project/update-range.ts
|
|
22262
|
+
function refused2(reason, message) {
|
|
22263
|
+
return { kind: "refused", reason, message };
|
|
22264
|
+
}
|
|
22265
|
+
function readCurrentRange(recorded, installed) {
|
|
22266
|
+
const to = parseSemanticVersion(installed);
|
|
22267
|
+
if (to === null)
|
|
22268
|
+
return refused2("unreadable", `the installed rafa answers "${installed}", which is no version`);
|
|
22269
|
+
if (recorded === null)
|
|
22270
|
+
return { kind: "adopt", to: installed };
|
|
22271
|
+
const from = parseSemanticVersion(recorded);
|
|
22272
|
+
if (from === null)
|
|
22273
|
+
return refused2("unreadable", `the project records rafa "${recorded}", which is no version`);
|
|
22274
|
+
const order = compareVersions(to, from);
|
|
22275
|
+
if (order < 0) {
|
|
22276
|
+
return refused2("older", `the installed rafa ${installed} is older than the project's ${recorded}, and no downgrade runs before 1.0.0`);
|
|
22277
|
+
}
|
|
22278
|
+
if (to.major !== from.major) {
|
|
22279
|
+
return refused2("newer-major", `the installed rafa ${installed} is a newer major than the project's ${recorded}; that move is rafa update latest`);
|
|
22280
|
+
}
|
|
22281
|
+
if (to.minor !== from.minor) {
|
|
22282
|
+
return refused2("newer-minor", `the installed rafa ${installed} is a newer minor than the project's ${recorded}; that move is rafa update next`);
|
|
22283
|
+
}
|
|
22284
|
+
return order === 0 ? { kind: "same", to: installed } : { kind: "patch", from: recorded, to: installed };
|
|
22285
|
+
}
|
|
22286
|
+
|
|
22287
|
+
// src/commands/update/current.ts
|
|
22288
|
+
var USAGE42 = "rafa update current [--dry-run] [--yes]";
|
|
22289
|
+
var APPLY_QUESTION = "Apply these changes? [y/N] ";
|
|
22290
|
+
var YES_ANSWERS5 = ["y", "yes"];
|
|
22291
|
+
function checkoutLockRoot(root, dir) {
|
|
22292
|
+
const run = Bun.spawnSync(["git", "rev-parse", "--show-toplevel"], { cwd: dir, stdout: "pipe", stderr: "pipe" });
|
|
22293
|
+
if (run.exitCode !== 0)
|
|
22294
|
+
return root;
|
|
22295
|
+
const top = run.stdout.toString().trim();
|
|
22296
|
+
return top !== "" && mainCheckoutOf(top) === root ? top : root;
|
|
22297
|
+
}
|
|
22298
|
+
var DEFAULT_UPDATE_CURRENT_SEAMS = Object.freeze({
|
|
22299
|
+
installed: RAFA_VERSION,
|
|
22300
|
+
openGh: (root) => createGhRunner({ cwd: root }),
|
|
22301
|
+
readRemote: gitRemoteUrl,
|
|
22302
|
+
isTerminal: () => process.stdin.isTTY === true,
|
|
22303
|
+
openPrompter: () => createLinePrompter(process.stdin, process.stderr),
|
|
22304
|
+
lockRootOf: (root) => checkoutLockRoot(root, process.cwd())
|
|
22305
|
+
});
|
|
22306
|
+
function readSwitch4(context, name) {
|
|
22307
|
+
const value = context.flags[name];
|
|
22308
|
+
if (value === undefined || value === false || value === "false")
|
|
22309
|
+
return false;
|
|
22310
|
+
if (value === true || value === "true")
|
|
22311
|
+
return true;
|
|
22312
|
+
throw new CommandExit(1, `\u274C --${name} takes no value, and read "${String(value)}" as one
|
|
22313
|
+
Usage: ${USAGE42}`);
|
|
22314
|
+
}
|
|
22315
|
+
function couldNotRun(problem2) {
|
|
22316
|
+
return new CommandExit(2, `FAIL \u2014 ${problem2}
|
|
22317
|
+
nothing was changed.`);
|
|
22318
|
+
}
|
|
22319
|
+
function projectOf15(context) {
|
|
22320
|
+
if (context.project === null)
|
|
22321
|
+
throw new Error("rafa update current runs inside a project, and was handed none");
|
|
22322
|
+
return context.project;
|
|
22323
|
+
}
|
|
22324
|
+
function boardAccess(project, seams) {
|
|
22325
|
+
let configured;
|
|
22326
|
+
try {
|
|
22327
|
+
configured = loadConfig({ root: project.root, home: project.home }, {}, () => {}).config.prProvider;
|
|
22328
|
+
} catch (error) {
|
|
22329
|
+
throw couldNotRun(`config: ${messageOf(error)}`);
|
|
22330
|
+
}
|
|
22331
|
+
const provider = resolvePrProvider({ configured, dir: project.root, readRemote: seams.readRemote }).provider;
|
|
22332
|
+
return provider === "gh" ? { gh: seams.openGh(project.root), reason: "" } : { gh: null, reason: `pr.provider is ${provider}, so there is no GitHub board` };
|
|
22333
|
+
}
|
|
22334
|
+
function foldersText(plan2) {
|
|
22335
|
+
if (plan2.folders.length === 0)
|
|
22336
|
+
return "nothing missing";
|
|
22337
|
+
return `create ${plan2.folders.join(", ")}`;
|
|
22338
|
+
}
|
|
22339
|
+
function boardText2(plan2) {
|
|
22340
|
+
if (plan2.board.kind === "skipped")
|
|
22341
|
+
return `skipped: ${plan2.board.reason}`;
|
|
22342
|
+
if (plan2.board.missing.length === 0)
|
|
22343
|
+
return "every label present";
|
|
22344
|
+
return `create labels ${plan2.board.missing.join(", ")}`;
|
|
22345
|
+
}
|
|
22346
|
+
function deprecationsText(plan2) {
|
|
22347
|
+
if (plan2.deprecations.length === 0)
|
|
22348
|
+
return "none registered yet (#718)";
|
|
22349
|
+
return `${String(plan2.deprecations.length)} to run`;
|
|
22350
|
+
}
|
|
22351
|
+
function lockText(plan2) {
|
|
22352
|
+
const where = plan2.lockRoot === plan2.root ? "" : ` in ${plan2.lockRoot}`;
|
|
22353
|
+
if (plan2.lock === "unchanged")
|
|
22354
|
+
return `unchanged at ${plan2.to}${where}`;
|
|
22355
|
+
if (plan2.lock === "created")
|
|
22356
|
+
return `create at ${plan2.to}${where}`;
|
|
22357
|
+
return `update ${String(plan2.from)} \u2192 ${plan2.to}${where}`;
|
|
22358
|
+
}
|
|
22359
|
+
function planLines2(plan2) {
|
|
22360
|
+
const from = plan2.from ?? `no ${LOCK_FILE}`;
|
|
22361
|
+
return [
|
|
22362
|
+
`rafa update current: ${plan2.root}, from ${from} to ${plan2.to}`,
|
|
22363
|
+
` folders ${foldersText(plan2)}`,
|
|
22364
|
+
` board ${boardText2(plan2)}`,
|
|
22365
|
+
` deprecations ${deprecationsText(plan2)}`,
|
|
22366
|
+
` ${LOCK_FILE} ${lockText(plan2)}`
|
|
22367
|
+
];
|
|
22368
|
+
}
|
|
22369
|
+
function appliedLines(applied, plan2, context) {
|
|
22370
|
+
for (const path of applied.folders)
|
|
22371
|
+
context.output.info(`created ${path}`);
|
|
22372
|
+
for (const part of applied.labels) {
|
|
22373
|
+
if (part.outcome === "created")
|
|
22374
|
+
context.output.info(`created label ${part.name}`);
|
|
22375
|
+
if (part.outcome === "refused")
|
|
22376
|
+
context.output.warn(`label ${part.name} was not created: ${part.detail}`);
|
|
22377
|
+
}
|
|
22378
|
+
if (applied.lock !== "unchanged")
|
|
22379
|
+
context.output.info(`wrote ${LOCK_FILE} at ${plan2.to}`);
|
|
22380
|
+
}
|
|
22381
|
+
async function confirmed2(yes, seams) {
|
|
22382
|
+
if (yes)
|
|
22383
|
+
return true;
|
|
22384
|
+
if (!seams.isTerminal()) {
|
|
22385
|
+
throw new CommandExit(1, "REFUSED \u2014 rafa update current asks before it changes anything, and there is no" + " terminal to ask on. Run it with --dry-run to see the changes, then with --yes to apply them." + `
|
|
22386
|
+
nothing was changed.`);
|
|
22387
|
+
}
|
|
22388
|
+
const prompter = seams.openPrompter();
|
|
22389
|
+
try {
|
|
22390
|
+
const answer2 = await prompter.ask(APPLY_QUESTION);
|
|
22391
|
+
return answer2 !== null && YES_ANSWERS5.includes(answer2.trim().toLowerCase());
|
|
22392
|
+
} finally {
|
|
22393
|
+
prompter.close();
|
|
22394
|
+
}
|
|
22395
|
+
}
|
|
22396
|
+
function readRange(root, installed) {
|
|
22397
|
+
let recorded;
|
|
22398
|
+
try {
|
|
22399
|
+
recorded = readProjectLock(root)?.rafa ?? null;
|
|
22400
|
+
} catch (error) {
|
|
22401
|
+
throw couldNotRun(messageOf(error));
|
|
22402
|
+
}
|
|
22403
|
+
const range = readCurrentRange(recorded, installed);
|
|
22404
|
+
if (range.kind === "refused")
|
|
22405
|
+
throw new CommandExit(1, `REFUSED \u2014 ${range.message}
|
|
22406
|
+
nothing was changed.`);
|
|
22407
|
+
return range;
|
|
22408
|
+
}
|
|
22409
|
+
function refuseConflicts(project) {
|
|
22410
|
+
const conflicts2 = scaffoldConflicts(project.root, project.home);
|
|
22411
|
+
if (conflicts2.length === 0)
|
|
22412
|
+
return;
|
|
22413
|
+
throw couldNotRun(`the project scope cannot be written:
|
|
22414
|
+
${conflicts2.map((conflict) => ` ${conflict}`).join(`
|
|
22415
|
+
`)}`);
|
|
22416
|
+
}
|
|
22417
|
+
function stoppedPartWay(error) {
|
|
22418
|
+
const kept = error.written.length === 0 ? "Nothing was written before it." : `Already written, and kept: ${error.written.join(", ")}.`;
|
|
22419
|
+
return new CommandExit(2, `FAIL \u2014 ${error.step}: ${error.message}
|
|
22420
|
+
stopped part-way. ${kept}` + `
|
|
22421
|
+
Fix the cause and run rafa update current again: each step writes only what is missing.`);
|
|
22422
|
+
}
|
|
22423
|
+
async function applyAndReport(plan2, gh, context) {
|
|
22424
|
+
let applied;
|
|
22425
|
+
try {
|
|
22426
|
+
applied = await applyUpdatePlan(plan2, gh);
|
|
22427
|
+
} catch (error) {
|
|
22428
|
+
if (error instanceof UpdateStepError)
|
|
22429
|
+
throw stoppedPartWay(error);
|
|
22430
|
+
throw error;
|
|
22431
|
+
}
|
|
22432
|
+
appliedLines(applied, plan2, context);
|
|
22433
|
+
return applied;
|
|
22434
|
+
}
|
|
22435
|
+
function refusedLabels(applied) {
|
|
22436
|
+
const refused3 = applied.labels.filter((part) => part.outcome === "refused");
|
|
22437
|
+
if (refused3.length === 0)
|
|
22438
|
+
return null;
|
|
22439
|
+
return new CommandExit(1, `${String(refused3.length)} board label(s) were not created; everything else was applied.` + " Run rafa update current again once gh can create them.");
|
|
22440
|
+
}
|
|
22441
|
+
function stopLine2(plan2, dryRun) {
|
|
22442
|
+
if (!planChanges(plan2))
|
|
22443
|
+
return "Nothing to change.";
|
|
22444
|
+
if (dryRun)
|
|
22445
|
+
return "Dry run: nothing was written. Run rafa update current to apply it.";
|
|
22446
|
+
return null;
|
|
22447
|
+
}
|
|
22448
|
+
async function runUpdateCurrent(context, seams) {
|
|
22449
|
+
expectNoArgument(context.args, USAGE42);
|
|
22450
|
+
const dryRun = readSwitch4(context, "dry-run");
|
|
22451
|
+
const yes = readSwitch4(context, "yes");
|
|
22452
|
+
const project = projectOf15(context);
|
|
22453
|
+
const lockRoot = seams.lockRootOf(project.root);
|
|
22454
|
+
const range = readRange(lockRoot, seams.installed);
|
|
22455
|
+
refuseConflicts(project);
|
|
22456
|
+
const access = boardAccess(project, seams);
|
|
22457
|
+
const plan2 = await readUpdatePlan({ root: project.root, lockRoot }, range, access);
|
|
22458
|
+
for (const line of planLines2(plan2))
|
|
22459
|
+
context.output.info(line);
|
|
22460
|
+
const finish = (applied2) => {
|
|
22461
|
+
if (context.outputMode === "json")
|
|
22462
|
+
context.output.result({ plan: plan2, applied: applied2 });
|
|
22463
|
+
};
|
|
22464
|
+
const stop = stopLine2(plan2, dryRun);
|
|
22465
|
+
if (stop !== null) {
|
|
22466
|
+
context.output.info(stop);
|
|
22467
|
+
finish(null);
|
|
22468
|
+
return;
|
|
22469
|
+
}
|
|
22470
|
+
context.output.warn("rafa cannot roll these changes back. Run it with --dry-run first to only see them.");
|
|
22471
|
+
if (!await confirmed2(yes, seams)) {
|
|
22472
|
+
context.output.info("Nothing was changed.");
|
|
22473
|
+
finish(null);
|
|
22474
|
+
return;
|
|
22475
|
+
}
|
|
22476
|
+
const applied = await applyAndReport(plan2, access.gh, context);
|
|
22477
|
+
finish(applied);
|
|
22478
|
+
const refusal13 = refusedLabels(applied);
|
|
22479
|
+
if (refusal13 !== null)
|
|
22480
|
+
throw refusal13;
|
|
22481
|
+
}
|
|
22482
|
+
function createUpdateCurrentCommand(seams = DEFAULT_UPDATE_CURRENT_SEAMS) {
|
|
22483
|
+
const command = {
|
|
22484
|
+
name: "update current",
|
|
22485
|
+
subject: "update",
|
|
22486
|
+
action: "current",
|
|
22487
|
+
summary: "bring this project to the installed rafa, within its patch range",
|
|
22488
|
+
description: "Brings the project to the installed rafa when the installed version shares the major and" + ` minor of the version \`${LOCK_FILE}\` records at the project root, and is the same patch or newer.` + " A project with no lock is adopted at the installed version. It creates the missing `.rafa/` folders" + " and the missing board labels on a GitHub board, runs the deprecations step (none registered yet)," + ` and writes \`${LOCK_FILE}\`. It prints every change first; \`--dry-run\` stops there. Otherwise it` + " warns that the changes cannot be rolled back and asks once, `--yes` answering; with no terminal and" + " no `--yes` it refuses with exit code 1. A newer minor or major, or an installed rafa older than the" + " lock, refuses with exit code 1, and so does a board label `gh` would not create, once everything" + " else is applied. A lock or config that cannot be read, or a `.rafa/` path holding something else," + " exits 2 before anything is asked; a folder or the lock that cannot be written exits 2 naming the" + " step and what was already written. In a linked worktree the lock is the worktree's, on its branch." + " With `--output=json` the plan and what was applied are the data of the terminal result event.",
|
|
22489
|
+
args: [],
|
|
22490
|
+
flags: [
|
|
22491
|
+
{ name: "dry-run", description: "Print every change and write nothing.", type: "boolean" },
|
|
22492
|
+
{ name: "yes", description: "Apply the changes without asking.", type: "boolean" }
|
|
22493
|
+
],
|
|
22494
|
+
examples: [
|
|
22495
|
+
{ cmd: "rafa update current --dry-run", note: "Lists the folders, labels and lock it would write, and writes nothing." },
|
|
22496
|
+
{ cmd: "rafa update current --yes", note: "Applies them without asking, as a script or a loop host runs it." }
|
|
22497
|
+
],
|
|
22498
|
+
outputs: ["text", "json"],
|
|
22499
|
+
run: async (context) => runUpdateCurrent(context, seams)
|
|
22500
|
+
};
|
|
22501
|
+
return Object.freeze(command);
|
|
22502
|
+
}
|
|
22503
|
+
var current_default2 = createUpdateCurrentCommand();
|
|
22504
|
+
|
|
22505
|
+
// src/commands/update/latest.ts
|
|
22506
|
+
var latest_default = createUpdateStub({ action: "latest", summary: "move this project to the newest version", issue: 716 });
|
|
22507
|
+
|
|
22508
|
+
// src/commands/update/next.ts
|
|
22509
|
+
var next_default2 = createUpdateStub({ action: "next", summary: "move this project to the newest minor of its major", issue: 716 });
|
|
22510
|
+
|
|
22511
|
+
// src/commands/update/port.ts
|
|
22512
|
+
var port_default = createUpdateStub({ action: "port", summary: "move this project to a version, as update project does", issue: 716, hidden: true });
|
|
22513
|
+
|
|
22514
|
+
// src/commands/update/project.ts
|
|
22515
|
+
var project_default = createUpdateStub({ action: "project", summary: "move this project to a version, with its migrations", issue: 716 });
|
|
22516
|
+
|
|
22517
|
+
// src/commands/update/rafa.ts
|
|
22518
|
+
var rafa_default = createUpdateStub({ action: "rafa", summary: "move this project to a version, as update project does", issue: 716, hidden: true });
|
|
22519
|
+
|
|
22520
|
+
// src/commands/update/self.ts
|
|
22521
|
+
var self_default = createUpdateStub({ action: "self", summary: "update rafa itself, from npm, a pinned version or another source", issue: 715 });
|
|
22522
|
+
|
|
21883
22523
|
// src/commands/usage.ts
|
|
21884
22524
|
var usage_default = wrapPhaseZeroCommand({
|
|
21885
22525
|
name: "usage",
|
|
@@ -21912,7 +22552,8 @@ var CORE_SUBJECTS = Object.freeze([
|
|
|
21912
22552
|
{ name: "release", summary: "read the release state of the project; settle the waiting fragments into a version; tag the commit that set it" },
|
|
21913
22553
|
{ name: "board", summary: "list the boards with their owner, epic count, and which is current and home" },
|
|
21914
22554
|
{ name: "epic", summary: "show one epic's issues as the Roadmap table; create an epic; defer or promote it; move an issue to it; close it through the gate or cancel it" },
|
|
21915
|
-
{ name: "claim", summary: "give up this device's claim on an issue; hand it to another store or withdraw the offer; accept a handover; take over a stale claim" }
|
|
22555
|
+
{ name: "claim", summary: "give up this device's claim on an issue; hand it to another store or withdraw the offer; accept a handover; take over a stale claim" },
|
|
22556
|
+
{ name: "update", summary: "bring this project to the installed rafa; the other updates are in development" }
|
|
21916
22557
|
]);
|
|
21917
22558
|
var CORE_COMMANDS = Object.freeze([
|
|
21918
22559
|
create_default2,
|
|
@@ -21984,6 +22625,14 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
21984
22625
|
hand_default,
|
|
21985
22626
|
accept_default,
|
|
21986
22627
|
take_default,
|
|
22628
|
+
current_default2,
|
|
22629
|
+
self_default,
|
|
22630
|
+
project_default,
|
|
22631
|
+
rafa_default,
|
|
22632
|
+
port_default,
|
|
22633
|
+
board_default,
|
|
22634
|
+
next_default2,
|
|
22635
|
+
latest_default,
|
|
21987
22636
|
status_default3,
|
|
21988
22637
|
next_default,
|
|
21989
22638
|
roadmap_default,
|