mergestorm 0.3.55 → 0.3.57
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 +2 -2
- package/dist/api.d.ts +13 -0
- package/dist/api.js +38 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/commands/issue.js +13 -1
- package/dist/commands/registry.js +4 -0
- package/dist/commands/settings.d.ts +10 -2
- package/dist/commands/settings.js +121 -3
- package/dist/stack-blockers.js +18 -1
- package/dist/stack-dto.d.ts +11 -3
- package/dist/ui/banner.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,12 +45,12 @@ On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL: a comp
|
|
|
45
45
|
|
|
46
46
|
```
|
|
47
47
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
48
|
-
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.
|
|
48
|
+
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.57 │
|
|
49
49
|
│ ▀▀▀▀▀▀▀▀▀ ● msk_live_… · maelstrom │
|
|
50
50
|
│ ▀▀▀▀▀▀ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used │
|
|
51
51
|
│ ▝▀▀▀▀▀ local reviews + stacked PRs · mergestorm.ai │
|
|
52
52
|
│ ▝▀▀▘ review a diff · usage for tabs · /help for all commands │
|
|
53
|
-
│ ▀ New in v0.3.
|
|
53
|
+
│ ▀ New in v0.3.57: issue create --help shows the usage │
|
|
54
54
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
55
55
|
mergestorm
|
|
56
56
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
package/dist/api.d.ts
CHANGED
|
@@ -175,6 +175,19 @@ export type SettingsResponse = import("./automation-catalog.js").BearerSettingsV
|
|
|
175
175
|
github_connected: boolean;
|
|
176
176
|
stacks_ready?: boolean;
|
|
177
177
|
};
|
|
178
|
+
export declare const REPO_SETTINGS_KEYS: readonly ["auto_patch_enabled", "auto_review_enabled", "auto_land_default"];
|
|
179
|
+
export type RepoSettingsKey = (typeof REPO_SETTINGS_KEYS)[number];
|
|
180
|
+
export type RepoSettings = {
|
|
181
|
+
owner: string;
|
|
182
|
+
repo: string;
|
|
183
|
+
} & Record<RepoSettingsKey, boolean | null>;
|
|
184
|
+
export type RepoSettingsPatch = Partial<Record<RepoSettingsKey, boolean | null>>;
|
|
185
|
+
export declare function listRepoSettings(cfg?: Config, opts?: {
|
|
186
|
+
timeoutMs?: number;
|
|
187
|
+
}): Promise<RepoSettings[]>;
|
|
188
|
+
export declare function patchRepoSettings(owner: string, repo: string, patch: RepoSettingsPatch, cfg?: Config, opts?: {
|
|
189
|
+
timeoutMs?: number;
|
|
190
|
+
}): Promise<RepoSettings>;
|
|
178
191
|
/**
|
|
179
192
|
* Fetch Bearer /api/v1/settings; null on 404/network/timeouts so panels can
|
|
180
193
|
* degrade (same contract as {@link getMe}), while 401 still surfaces.
|
package/dist/api.js
CHANGED
|
@@ -172,6 +172,44 @@ export function applyPrReviewQuery(query, opts) {
|
|
|
172
172
|
* The connected flags are read-only there and deliberately absent here.
|
|
173
173
|
*/
|
|
174
174
|
export { SETTINGS_WRITABLE_KEYS };
|
|
175
|
+
export const REPO_SETTINGS_KEYS = ["auto_patch_enabled", "auto_review_enabled", "auto_land_default"];
|
|
176
|
+
function repoSettingsFailure(status, body, retryAfterSeconds, action) {
|
|
177
|
+
if (status === 429) {
|
|
178
|
+
return new CommandError(rateLimitedMessage(retryAfterSeconds), REVIEW_EXIT.rate_limited, "rate_limited", {
|
|
179
|
+
retryAfterSeconds,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const message = body?.message;
|
|
183
|
+
if (typeof message === "string" && message.trim())
|
|
184
|
+
return new CommandError(message);
|
|
185
|
+
if (status === 404) {
|
|
186
|
+
return new CommandError("Repository settings are not available on this server yet. Deploy the API update or use the dashboard.");
|
|
187
|
+
}
|
|
188
|
+
return new CommandError(`Failed to ${action} repository settings (HTTP ${status}): ${JSON.stringify(body)}`);
|
|
189
|
+
}
|
|
190
|
+
export async function listRepoSettings(cfg, opts) {
|
|
191
|
+
const resolved = cfg ?? (await loadConfig());
|
|
192
|
+
const { status, body, retryAfterSeconds } = await apiFetch(resolved, "/api/v1/settings/repos", {
|
|
193
|
+
timeoutMs: opts?.timeoutMs,
|
|
194
|
+
});
|
|
195
|
+
if (status !== 200)
|
|
196
|
+
throw repoSettingsFailure(status, body, retryAfterSeconds, "load");
|
|
197
|
+
const repos = body?.repos;
|
|
198
|
+
if (!Array.isArray(repos)) {
|
|
199
|
+
throw new CommandError(`Repository settings came back in an unexpected shape: ${JSON.stringify(body)}`);
|
|
200
|
+
}
|
|
201
|
+
return repos;
|
|
202
|
+
}
|
|
203
|
+
export async function patchRepoSettings(owner, repo, patch, cfg, opts) {
|
|
204
|
+
const resolved = cfg ?? (await loadConfig());
|
|
205
|
+
if (Object.keys(patch).length === 0) {
|
|
206
|
+
throw new CommandError("No repository settings to update.", 2, "usage");
|
|
207
|
+
}
|
|
208
|
+
const { status, body, retryAfterSeconds } = await apiFetch(resolved, `/api/v1/settings/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, { method: "PATCH", json: patch, timeoutMs: opts?.timeoutMs });
|
|
209
|
+
if (status !== 200)
|
|
210
|
+
throw repoSettingsFailure(status, body, retryAfterSeconds, "update");
|
|
211
|
+
return body;
|
|
212
|
+
}
|
|
175
213
|
/**
|
|
176
214
|
* Fetch Bearer /api/v1/settings; null on 404/network/timeouts so panels can
|
|
177
215
|
* degrade (same contract as {@link getMe}), while 401 still surfaces.
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig, mergestormHome, resolveApiKey, saveConfig, type Config, } from "../config.js";
|
|
2
2
|
export { BEARER_SETTINGS, BEARER_BOOLEAN_SETTINGS_KEYS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, type BearerSettingsKey, } from "../automation-catalog.js";
|
|
3
|
-
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, getStackPublication, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, openStackIssue, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, submitStackPublication, type ApiFetchInit, type ApiFetchResult, type JobListItem, type MeResponse, type MergeQueueBounceDetail, type MergeQueueBounceKind, type MergeQueueEnqueuedBy, type MergeQueueEnqueuedVia, type MergeQueueEntryDto, type MergeQueueEntryState, type PrDismissedFinding, type PrFindingDismissInput, type PrFindingDismissOutcome, type PrFindingDismissResult, type PrReviewFinding, type PrReviewFindings, type PrVortexReview, type SettingsPatch, type SettingsResponse, type SettingsWritableKey, type StackDto, type StackPolicyPatch, type StackPublication, type StackPublicationRequest, type StackPublicationStatus, type ThreadDetail, type ThreadListItem, } from "../api.js";
|
|
3
|
+
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, listRepoSettings, patchRepoSettings, REPO_SETTINGS_KEYS, getStackPublication, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, openStackIssue, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, submitStackPublication, type ApiFetchInit, type ApiFetchResult, type JobListItem, type MeResponse, type MergeQueueBounceDetail, type MergeQueueBounceKind, type MergeQueueEnqueuedBy, type MergeQueueEnqueuedVia, type MergeQueueEntryDto, type MergeQueueEntryState, type PrDismissedFinding, type PrFindingDismissInput, type PrFindingDismissOutcome, type PrFindingDismissResult, type PrReviewFinding, type PrReviewFindings, type PrVortexReview, type SettingsPatch, type RepoSettings, type RepoSettingsKey, type RepoSettingsPatch, type SettingsResponse, type SettingsWritableKey, type StackDto, type StackPolicyPatch, type StackPublication, type StackPublicationRequest, type StackPublicationStatus, type ThreadDetail, type ThreadListItem, } from "../api.js";
|
|
4
4
|
export { CommandError, DetachedError, RATE_LIMITED_HINT, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage, type CommandErrorCode, type CommandErrorExtras, } from "../errors.js";
|
|
5
5
|
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, noPrReviewYetMessage, collectReviewInput, formatReviewSubmitError, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, type PollReviewOptions, type PollPrVortexReviewOptions, type ReviewInput, type SubmitReviewOptions, } from "../commands/review-client.js";
|
|
6
6
|
export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, type ReviewEnvelopeFallbacks, type ReviewJobEnvelope, type ReviewJobRow, } from "../ui/job-envelope.js";
|
package/dist/client/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig, mergestormHome, resolveApiKey, saveConfig, } from "../config.js";
|
|
2
2
|
export { BEARER_SETTINGS, BEARER_BOOLEAN_SETTINGS_KEYS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, } from "../automation-catalog.js";
|
|
3
|
-
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, getStackPublication, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, openStackIssue, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, submitStackPublication, } from "../api.js";
|
|
3
|
+
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, listRepoSettings, patchRepoSettings, REPO_SETTINGS_KEYS, getStackPublication, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, openStackIssue, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, submitStackPublication, } from "../api.js";
|
|
4
4
|
export { CommandError, DetachedError, RATE_LIMITED_HINT, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage, } from "../errors.js";
|
|
5
5
|
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, noPrReviewYetMessage, collectReviewInput, formatReviewSubmitError, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, } from "../commands/review-client.js";
|
|
6
6
|
export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, } from "../ui/job-envelope.js";
|
package/dist/commands/issue.js
CHANGED
|
@@ -9,6 +9,7 @@ export const ISSUE_USAGE = [
|
|
|
9
9
|
" Opens a GitHub issue through Mergestorm as the Surge App. Needs no gh and no GitHub login.",
|
|
10
10
|
" Uses existing labels only. Running it again with the same title returns the open issue it already made when that issue is among the newest 100 open issues; an older one, or two calls at once, can still create a duplicate.",
|
|
11
11
|
].join("\n");
|
|
12
|
+
const VALUE_FLAGS = new Set(["--title", "--body", "--body-file", "--label", "--repo"]);
|
|
12
13
|
function usage(message) {
|
|
13
14
|
return new CommandError(message ? `${message}\n${ISSUE_USAGE}` : ISSUE_USAGE, REVIEW_EXIT.usage, "usage");
|
|
14
15
|
}
|
|
@@ -65,7 +66,18 @@ export function parseIssueCreateArgs(args, readBody = (path) => readFileSync(pat
|
|
|
65
66
|
}
|
|
66
67
|
export async function cmdIssue(args, deps = {}) {
|
|
67
68
|
const sub = args[0]?.toLowerCase();
|
|
68
|
-
|
|
69
|
+
const asksForHelp = (arg) => arg === "-h" || arg === "--help" || arg === "help";
|
|
70
|
+
const createAsksForHelp = (rest) => {
|
|
71
|
+
for (let i = 0; i < rest.length; i++) {
|
|
72
|
+
const arg = rest[i];
|
|
73
|
+
if (asksForHelp(arg))
|
|
74
|
+
return true;
|
|
75
|
+
if (VALUE_FLAGS.has(arg))
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
};
|
|
80
|
+
if (!sub || asksForHelp(sub) || (sub === "create" && createAsksForHelp(args.slice(1)))) {
|
|
69
81
|
console.log(ISSUE_USAGE);
|
|
70
82
|
return;
|
|
71
83
|
}
|
|
@@ -208,6 +208,10 @@ export const COMMAND_REGISTRY = [
|
|
|
208
208
|
" [--vortex-skip-check none|neutral] [--vortex-findings failure|neutral|success]",
|
|
209
209
|
" [--cyclone-fail-check failure|neutral]",
|
|
210
210
|
" With flags: PATCH those settings and print the stored result",
|
|
211
|
+
"mergestorm settings --repos [--json] Repository-level Auto patch, Auto review and Auto land for every connected repository",
|
|
212
|
+
"mergestorm settings --repo <owner>/<repo> [--auto-patch on|off|inherit] [--auto-review on|off|inherit] [--auto-land on|off|inherit] [--json]",
|
|
213
|
+
" A repository value beats the account setting; a stack override (mergestorm stack set) beats the repository.",
|
|
214
|
+
" inherit clears the repository value so the account setting applies. --auto-land seeds new stacks only.",
|
|
211
215
|
],
|
|
212
216
|
async run(args, ctx) {
|
|
213
217
|
await cmdSettings(args, { mode: ctx.mode });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SettingsPatch, type SettingsResponse } from "../api.js";
|
|
1
|
+
import { type RepoSettings, type RepoSettingsPatch, type SettingsPatch, type SettingsResponse } from "../api.js";
|
|
2
2
|
/**
|
|
3
3
|
* CLI flag → Bearer settings key. Boolean flags take on|off. The connected flags are
|
|
4
4
|
* read-only on the API and deliberately have no flag here.
|
|
@@ -10,13 +10,21 @@ type IgnoreBotOperation = {
|
|
|
10
10
|
} | {
|
|
11
11
|
action: "clear";
|
|
12
12
|
};
|
|
13
|
+
export type RepoSettingsScope = {
|
|
14
|
+
owner: string;
|
|
15
|
+
repo: string;
|
|
16
|
+
patch: RepoSettingsPatch;
|
|
17
|
+
};
|
|
13
18
|
export type SettingsArgs = {
|
|
14
19
|
json: boolean;
|
|
15
20
|
patch: SettingsPatch;
|
|
16
21
|
ignoreBots?: IgnoreBotOperation[];
|
|
22
|
+
repo?: RepoSettingsScope;
|
|
23
|
+
listRepos?: boolean;
|
|
17
24
|
};
|
|
25
|
+
export declare function formatRepoSettingsLines(repos: readonly RepoSettings[]): string[];
|
|
18
26
|
/** Parse boolean and enum flags plus ordered ignore-list operations. */
|
|
19
|
-
export declare function parseSettingsArgs(
|
|
27
|
+
export declare function parseSettingsArgs(rawArgs: string[]): SettingsArgs;
|
|
20
28
|
/** Static printout includes the Config toggles plus enum and login settings. */
|
|
21
29
|
export declare function formatSettingsLines(settings: SettingsResponse): string[];
|
|
22
30
|
export declare function cmdSettings(args: string[], opts?: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSettings, patchSettings, } from "../api.js";
|
|
1
|
+
import { getSettings, listRepoSettings, patchRepoSettings, patchSettings, REPO_SETTINGS_KEYS, } from "../api.js";
|
|
2
2
|
import { BEARER_SETTINGS, BEARER_SETTINGS_FLAGS } from "../automation-catalog.js";
|
|
3
3
|
import { loadConfig } from "../config.js";
|
|
4
4
|
import { CommandError } from "../errors.js";
|
|
@@ -10,6 +10,107 @@ import { buildConfigRows, canBrowse, openTabsBrowser } from "./browse.js";
|
|
|
10
10
|
* read-only on the API and deliberately have no flag here.
|
|
11
11
|
*/
|
|
12
12
|
export const SETTINGS_FLAGS = BEARER_SETTINGS_FLAGS;
|
|
13
|
+
const REPO_FLAG_LABELS = {
|
|
14
|
+
auto_patch_enabled: "Auto patch",
|
|
15
|
+
auto_review_enabled: "Auto review",
|
|
16
|
+
auto_land_default: "Auto land (new stacks)",
|
|
17
|
+
};
|
|
18
|
+
function parseRepoSlug(raw) {
|
|
19
|
+
const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(raw ?? "");
|
|
20
|
+
if (!match)
|
|
21
|
+
throw new CommandError("--repo takes <owner>/<repo>.", 2, "usage");
|
|
22
|
+
return { owner: match[1], repo: match[2] };
|
|
23
|
+
}
|
|
24
|
+
function repoArg(args) {
|
|
25
|
+
const rest = [];
|
|
26
|
+
let slug;
|
|
27
|
+
let found = false;
|
|
28
|
+
let listRepos = false;
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
if (arg === "--repos")
|
|
32
|
+
listRepos = true;
|
|
33
|
+
else if (arg === "--repo") {
|
|
34
|
+
found = true;
|
|
35
|
+
slug = args[++i];
|
|
36
|
+
}
|
|
37
|
+
else if (arg.startsWith("--repo=")) {
|
|
38
|
+
found = true;
|
|
39
|
+
slug = arg.slice("--repo=".length);
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
rest.push(arg);
|
|
43
|
+
}
|
|
44
|
+
if (found && slug === undefined)
|
|
45
|
+
slug = "";
|
|
46
|
+
return { slug, rest, listRepos };
|
|
47
|
+
}
|
|
48
|
+
function parseRepoSettingsArgs(slug, args) {
|
|
49
|
+
const scope = { ...parseRepoSlug(slug), patch: {} };
|
|
50
|
+
let json = false;
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
const arg = args[i];
|
|
53
|
+
if (arg === "--json") {
|
|
54
|
+
json = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const eq = arg.indexOf("=");
|
|
58
|
+
const flag = eq >= 0 ? arg.slice(0, eq) : arg;
|
|
59
|
+
const key = SETTINGS_FLAGS[flag];
|
|
60
|
+
if (!key) {
|
|
61
|
+
throw new CommandError(`Unknown settings flag: ${flag}. See \`mergestorm --help\`.`, 2, "usage");
|
|
62
|
+
}
|
|
63
|
+
if (!REPO_SETTINGS_KEYS.includes(key)) {
|
|
64
|
+
throw new CommandError(`${flag} is an account setting. With --repo only --auto-patch, --auto-review and --auto-land can be set.`, 2, "usage");
|
|
65
|
+
}
|
|
66
|
+
const value = eq >= 0 ? arg.slice(eq + 1) : args[++i];
|
|
67
|
+
if (value !== "on" && value !== "off" && value !== "inherit") {
|
|
68
|
+
throw new CommandError(`${flag} takes on, off or inherit with --repo.`, 2, "usage");
|
|
69
|
+
}
|
|
70
|
+
scope.patch[key] = value === "inherit" ? null : value === "on";
|
|
71
|
+
}
|
|
72
|
+
return { json, patch: {}, repo: scope };
|
|
73
|
+
}
|
|
74
|
+
function repoSettingWords(value) {
|
|
75
|
+
return value === null ? "inherit (follows the account)" : value ? "on" : "off";
|
|
76
|
+
}
|
|
77
|
+
export function formatRepoSettingsLines(repos) {
|
|
78
|
+
if (repos.length === 0)
|
|
79
|
+
return [" No repositories are connected to this account."];
|
|
80
|
+
const width = Math.max(...Object.values(REPO_FLAG_LABELS).map((label) => label.length));
|
|
81
|
+
return repos.flatMap((entry) => [
|
|
82
|
+
` ${entry.owner}/${entry.repo}`,
|
|
83
|
+
...REPO_SETTINGS_KEYS.map((key) => ` ${REPO_FLAG_LABELS[key].padEnd(width + 2)}${repoSettingWords(entry[key])}`),
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
async function cmdRepoSettings(parsed, opts) {
|
|
87
|
+
const cfg = await loadConfig();
|
|
88
|
+
let repos;
|
|
89
|
+
const scope = parsed.repo;
|
|
90
|
+
if (scope && Object.keys(scope.patch).length > 0) {
|
|
91
|
+
repos = [await patchRepoSettings(scope.owner, scope.repo, scope.patch, cfg)];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
repos = await listRepoSettings(cfg);
|
|
95
|
+
if (scope) {
|
|
96
|
+
const wanted = `${scope.owner}/${scope.repo}`.toLowerCase();
|
|
97
|
+
repos = repos.filter((entry) => `${entry.owner}/${entry.repo}`.toLowerCase() === wanted);
|
|
98
|
+
if (repos.length === 0) {
|
|
99
|
+
throw new CommandError(`${scope.owner}/${scope.repo} is not connected to this account.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (parsed.json) {
|
|
104
|
+
const text = JSON.stringify(scope ? repos[0] : { repos }, null, 2);
|
|
105
|
+
if (opts.mode === "shell") {
|
|
106
|
+
await present("Repository settings", text.split("\n"));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
console.log(text);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await present("Repository settings", formatRepoSettingsLines(repos));
|
|
113
|
+
}
|
|
13
114
|
function canonicalLogin(raw) {
|
|
14
115
|
const login = raw.trim().toLowerCase().replace(/\[bot\]$/, "");
|
|
15
116
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(login)) {
|
|
@@ -18,7 +119,19 @@ function canonicalLogin(raw) {
|
|
|
18
119
|
return login;
|
|
19
120
|
}
|
|
20
121
|
/** Parse boolean and enum flags plus ordered ignore-list operations. */
|
|
21
|
-
export function parseSettingsArgs(
|
|
122
|
+
export function parseSettingsArgs(rawArgs) {
|
|
123
|
+
const { slug, rest: args, listRepos } = repoArg(rawArgs);
|
|
124
|
+
if (slug !== undefined) {
|
|
125
|
+
if (listRepos)
|
|
126
|
+
throw new CommandError("--repos and --repo cannot be combined.", 2, "usage");
|
|
127
|
+
return parseRepoSettingsArgs(slug, args);
|
|
128
|
+
}
|
|
129
|
+
if (listRepos) {
|
|
130
|
+
const other = args.find((arg) => arg !== "--json");
|
|
131
|
+
if (other)
|
|
132
|
+
throw new CommandError(`--repos lists repository settings and takes only --json, not ${other}.`, 2, "usage");
|
|
133
|
+
return { json: args.includes("--json"), patch: {}, listRepos: true };
|
|
134
|
+
}
|
|
22
135
|
let json = false;
|
|
23
136
|
const ignoreBots = [];
|
|
24
137
|
const patch = {};
|
|
@@ -97,7 +210,12 @@ export function formatSettingsLines(settings) {
|
|
|
97
210
|
});
|
|
98
211
|
}
|
|
99
212
|
export async function cmdSettings(args, opts = {}) {
|
|
100
|
-
const
|
|
213
|
+
const parsed = parseSettingsArgs(args);
|
|
214
|
+
if (parsed.repo || parsed.listRepos) {
|
|
215
|
+
await cmdRepoSettings(parsed, opts);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const { json, patch, ignoreBots } = parsed;
|
|
101
219
|
const hasPatch = Object.keys(patch).length > 0 || Boolean(ignoreBots?.length);
|
|
102
220
|
// Bare `settings` on a TTY: the tabbed browser, opened on Config.
|
|
103
221
|
if (!hasPatch && !json && canBrowse()) {
|
package/dist/stack-blockers.js
CHANGED
|
@@ -81,6 +81,8 @@ const CYCLONE_HANDOFF_WORDS = {
|
|
|
81
81
|
failed: "Cyclone's patch failed, so it pushed nothing",
|
|
82
82
|
auto_patch_off: "auto-patch is off for this stack",
|
|
83
83
|
stack_auto_patch_off: "auto-patch is off for this stack",
|
|
84
|
+
repo_auto_patch_off: "auto-patch is off for this repository and this stack does not turn it on",
|
|
85
|
+
repo_setting_unavailable: "the repository's auto-patch setting could not be read, so Cyclone will not patch until it can",
|
|
84
86
|
account_auto_patch_off: "auto-patch is off for the account and this stack does not turn it on",
|
|
85
87
|
cyclone_not_connected: "Cyclone is not connected to this repo",
|
|
86
88
|
infra_failure: "Cyclone could not start the patch: GitHub sign-in, comment load or checkout kept failing",
|
|
@@ -220,7 +222,22 @@ function vortexSkippedSteps(stack, layer, skipReason) {
|
|
|
220
222
|
const head = layer.headSha ?? "its live head";
|
|
221
223
|
const mention = `comment @mergestorm-vortex review on #${layer.prNumber}`;
|
|
222
224
|
if (skipReason === "auto_review_off") {
|
|
223
|
-
|
|
225
|
+
const slug = `${stack.owner}/${stack.repo}`;
|
|
226
|
+
const where = stack.vortexReviewOff === "stack_auto_review_off"
|
|
227
|
+
? "for this stack"
|
|
228
|
+
: stack.vortexReviewOff === "repo_auto_review_off"
|
|
229
|
+
? `for the repository ${slug}`
|
|
230
|
+
: stack.vortexReviewOff === "account_auto_review_off"
|
|
231
|
+
? "for the account"
|
|
232
|
+
: `for ${slug} or for this stack`;
|
|
233
|
+
const lead = `Vortex auto-review is off ${where}, so it skipped #${layer.prNumber} at ${head} and Auto land waits for a review that will not come. Do not patch or push for this.`;
|
|
234
|
+
if (stack.vortexReviewOff === "repo_auto_review_off") {
|
|
235
|
+
return `${lead} Tell the human: turning auto-review on for this stack only (mg stack set ${stack.id} --auto-review on) queues a review of this head by itself. Turning it on for the repository (mg settings --repo ${slug} --auto-review on) applies from the next push and queues nothing, so this head still needs a mention. To review only this head and leave auto-review off, ${mention}.`;
|
|
236
|
+
}
|
|
237
|
+
const sinceTurnedOn = stack.vortexReviewOff
|
|
238
|
+
? ""
|
|
239
|
+
: ` If auto-review has been turned back on since this head was pushed, the skip is from before that and nothing re-queues it: ${mention}.`;
|
|
240
|
+
return `${lead} Tell the human: turning auto-review on (mg stack set ${stack.id} --auto-review on, or the account setting in the dashboard) queues a review of this head by itself. To review only this head and leave auto-review off, ${mention}.${sinceTurnedOn}`;
|
|
224
241
|
}
|
|
225
242
|
if (skipReason === "ignored_author") {
|
|
226
243
|
return `Vortex skipped #${layer.prNumber} at ${head} because the PR's author is on the account's ignored authors list, so Auto land waits for a review that will not come. Do not patch or push for this. To review this head once, ${mention}. To have this author's PRs reviewed by themselves, tell the human to remove the author from the ignored authors list in the Mergestorm dashboard settings.`;
|
package/dist/stack-dto.d.ts
CHANGED
|
@@ -313,7 +313,8 @@ export type StackUnitDto = {
|
|
|
313
313
|
landPr: StackLayerDto | null;
|
|
314
314
|
};
|
|
315
315
|
export type CycloneOwnerMatch = "same" | "different" | "none" | "lookup_failed";
|
|
316
|
-
export type CyclonePatchOffReason = "stack_auto_patch_off" | "account_auto_patch_off" | "cyclone_not_connected";
|
|
316
|
+
export type CyclonePatchOffReason = "stack_auto_patch_off" | "repo_auto_patch_off" | "repo_setting_unavailable" | "account_auto_patch_off" | "cyclone_not_connected";
|
|
317
|
+
export type VortexReviewOffReason = "stack_auto_review_off" | "repo_auto_review_off" | "account_auto_review_off";
|
|
317
318
|
export type CycloneConflictOffReason = "stack_auto_resolve_off" | "account_auto_resolve_off" | "cyclone_not_connected";
|
|
318
319
|
export type CycloneCiOffReason = "stack_auto_fix_ci_off" | "account_auto_fix_ci_off" | "cyclone_not_connected";
|
|
319
320
|
export type StackDto = {
|
|
@@ -364,12 +365,14 @@ export type StackDto = {
|
|
|
364
365
|
} | null;
|
|
365
366
|
/**
|
|
366
367
|
* Per-stack Vortex auto-review override. `null` (or missing) follows the
|
|
367
|
-
* account `auto_review_enabled` flag; a
|
|
368
|
+
* repository setting and then the account `auto_review_enabled` flag; a
|
|
369
|
+
* boolean wins in both directions.
|
|
368
370
|
*/
|
|
369
371
|
autoReviewOverride?: boolean | null;
|
|
370
372
|
/**
|
|
371
373
|
* Per-stack Cyclone auto-patch override. `null` (or missing) follows the
|
|
372
|
-
* account `auto_patch_enabled` flag; a
|
|
374
|
+
* repository setting and then the account `auto_patch_enabled` flag; a
|
|
375
|
+
* boolean wins in both directions.
|
|
373
376
|
*/
|
|
374
377
|
autoPatchOverride?: boolean | null;
|
|
375
378
|
autoResolveConflictsOverride?: boolean | null;
|
|
@@ -395,6 +398,11 @@ export type StackDto = {
|
|
|
395
398
|
*/
|
|
396
399
|
cycloneOwnerMatch?: CycloneOwnerMatch;
|
|
397
400
|
cyclonePatchOff?: CyclonePatchOffReason;
|
|
401
|
+
/**
|
|
402
|
+
* Which level turned Auto review off for this stack: its own override, the
|
|
403
|
+
* repository setting, or the account. Absent while Auto review is on.
|
|
404
|
+
*/
|
|
405
|
+
vortexReviewOff?: VortexReviewOffReason;
|
|
398
406
|
cycloneConflictOff?: CycloneConflictOffReason;
|
|
399
407
|
cycloneCiOff?: CycloneCiOffReason;
|
|
400
408
|
infrastructureInstallUserId?: string | null;
|
package/dist/ui/banner.js
CHANGED
|
@@ -203,6 +203,12 @@ const WHATS_NEW = {
|
|
|
203
203
|
"0.3.55": [
|
|
204
204
|
"MERGESTORM_HOME moves login and stack state",
|
|
205
205
|
],
|
|
206
|
+
"0.3.56": [
|
|
207
|
+
"Set automation per repository",
|
|
208
|
+
],
|
|
209
|
+
"0.3.57": [
|
|
210
|
+
"issue create --help shows the usage",
|
|
211
|
+
],
|
|
206
212
|
};
|
|
207
213
|
function densityFromVariant(variant) {
|
|
208
214
|
if (variant === "mini" || variant === "nano")
|
package/package.json
CHANGED