merge-steward 0.8.13 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +4 -0
- package/dist/config.js +13 -0
- package/dist/github/shell-git.d.ts +9 -4
- package/dist/github/shell-git.js +28 -16
- package/dist/server.js +2 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export declare const stewardConfigSchema: z.ZodObject<{
|
|
|
31
31
|
admissionLabel: z.ZodDefault<z.ZodString>;
|
|
32
32
|
mergeQueueCheckName: z.ZodDefault<z.ZodString>;
|
|
33
33
|
excludeBranches: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
34
|
+
autoResolvePatterns: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
35
|
+
glob: z.ZodString;
|
|
36
|
+
command: z.ZodArray<z.ZodString>;
|
|
37
|
+
}, z.core.$strip>>>;
|
|
34
38
|
webhookSecret: z.ZodOptional<z.ZodString>;
|
|
35
39
|
}, z.core.$strip>;
|
|
36
40
|
export type StewardConfig = z.infer<typeof stewardConfigSchema>;
|
package/dist/config.js
CHANGED
|
@@ -33,6 +33,19 @@ export const stewardConfigSchema = z.object({
|
|
|
33
33
|
mergeQueueCheckName: z.string().default(DEFAULT_MERGE_QUEUE_CHECK_NAME),
|
|
34
34
|
/** Branch name patterns to exclude from admission (glob-style). */
|
|
35
35
|
excludeBranches: z.array(z.string()).default(["release-please--*"]),
|
|
36
|
+
/**
|
|
37
|
+
* File patterns with regen commands for auto-resolving merge conflicts.
|
|
38
|
+
* When all conflicting files match a pattern, the command is run to regenerate them.
|
|
39
|
+
* Defaults to lockfile resolution for npm/pnpm/yarn.
|
|
40
|
+
*/
|
|
41
|
+
autoResolvePatterns: z.array(z.object({
|
|
42
|
+
glob: z.string().min(1),
|
|
43
|
+
command: z.array(z.string()).min(1),
|
|
44
|
+
})).default([
|
|
45
|
+
{ glob: "**/package-lock.json", command: ["npm", "install", "--package-lock-only"] },
|
|
46
|
+
{ glob: "**/pnpm-lock.yaml", command: ["pnpm", "install", "--lockfile-only"] },
|
|
47
|
+
{ glob: "**/yarn.lock", command: ["yarn", "install", "--mode", "update-lockfile"] },
|
|
48
|
+
]),
|
|
36
49
|
webhookSecret: z.string().optional(),
|
|
37
50
|
});
|
|
38
51
|
function readEnvFile(filePath) {
|
|
@@ -4,14 +4,20 @@ export interface BotIdentity {
|
|
|
4
4
|
name: string;
|
|
5
5
|
email: string;
|
|
6
6
|
}
|
|
7
|
+
export interface AutoResolvePattern {
|
|
8
|
+
glob: string;
|
|
9
|
+
command: string[];
|
|
10
|
+
}
|
|
7
11
|
export declare class ShellGitOperations implements GitOperations, SpeculativeBranchBuilder {
|
|
8
12
|
private readonly clonePath;
|
|
9
13
|
private readonly repoFullName;
|
|
10
14
|
private readonly gitBin;
|
|
11
15
|
private readonly worktreeBase;
|
|
12
16
|
private botIdentity;
|
|
17
|
+
private autoResolvePatterns;
|
|
13
18
|
constructor(clonePath: string, repoFullName: string, gitBin?: string);
|
|
14
19
|
setBotIdentity(identity: BotIdentity): void;
|
|
20
|
+
setAutoResolvePatterns(patterns: AutoResolvePattern[]): void;
|
|
15
21
|
private git;
|
|
16
22
|
/** Run a git command in a specific directory (worktree). */
|
|
17
23
|
private gitIn;
|
|
@@ -27,10 +33,9 @@ export declare class ShellGitOperations implements GitOperations, SpeculativeBra
|
|
|
27
33
|
*/
|
|
28
34
|
buildSpeculative(prBranch: string, baseBranch: string, specName: string, mergeMessage?: string): Promise<MergeResult>;
|
|
29
35
|
/**
|
|
30
|
-
* During a merge conflict, check if
|
|
31
|
-
* If so,
|
|
32
|
-
* appropriate package manager.
|
|
36
|
+
* During a merge conflict, check if all unmerged files match a configured
|
|
37
|
+
* auto-resolve pattern. If so, regenerate them via the pattern's command.
|
|
33
38
|
*/
|
|
34
|
-
private
|
|
39
|
+
private tryAutoResolveConflict;
|
|
35
40
|
deleteSpeculative(specName: string): Promise<void>;
|
|
36
41
|
}
|
package/dist/github/shell-git.js
CHANGED
|
@@ -12,16 +12,23 @@ function parseConflicts(stderr) {
|
|
|
12
12
|
});
|
|
13
13
|
return files.length > 0 ? files : undefined;
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
/** Check if a file path matches a glob pattern (simple suffix/wildcard match). */
|
|
16
|
+
function matchesGlob(filePath, pattern) {
|
|
17
|
+
if (pattern.startsWith("**/")) {
|
|
18
|
+
return filePath.endsWith(pattern.slice(2));
|
|
19
|
+
}
|
|
20
|
+
if (pattern.startsWith("*/")) {
|
|
21
|
+
const name = filePath.split("/").pop() ?? filePath;
|
|
22
|
+
return name === pattern.slice(2);
|
|
23
|
+
}
|
|
24
|
+
return filePath === pattern || filePath.endsWith(`/${pattern}`);
|
|
25
|
+
}
|
|
26
|
+
/** If all conflicting files match a single auto-resolve pattern, return its command. */
|
|
27
|
+
function detectAutoResolveCommand(conflictFiles, patterns) {
|
|
28
|
+
for (const pattern of patterns) {
|
|
29
|
+
if (conflictFiles.every((f) => matchesGlob(f, pattern.glob))) {
|
|
30
|
+
const [bin, ...args] = pattern.command;
|
|
31
|
+
return bin ? { bin, args } : undefined;
|
|
25
32
|
}
|
|
26
33
|
}
|
|
27
34
|
return undefined;
|
|
@@ -32,6 +39,7 @@ export class ShellGitOperations {
|
|
|
32
39
|
gitBin;
|
|
33
40
|
worktreeBase;
|
|
34
41
|
botIdentity;
|
|
42
|
+
autoResolvePatterns = [];
|
|
35
43
|
constructor(clonePath, repoFullName, gitBin = "git") {
|
|
36
44
|
this.clonePath = clonePath;
|
|
37
45
|
this.repoFullName = repoFullName;
|
|
@@ -41,6 +49,9 @@ export class ShellGitOperations {
|
|
|
41
49
|
setBotIdentity(identity) {
|
|
42
50
|
this.botIdentity = identity;
|
|
43
51
|
}
|
|
52
|
+
setAutoResolvePatterns(patterns) {
|
|
53
|
+
this.autoResolvePatterns = patterns;
|
|
54
|
+
}
|
|
44
55
|
git(args, opts) {
|
|
45
56
|
return exec(this.gitBin, ["-C", this.clonePath, ...args], {
|
|
46
57
|
timeoutMs: opts?.timeoutMs ?? 120_000,
|
|
@@ -112,7 +123,7 @@ export class ShellGitOperations {
|
|
|
112
123
|
if (result.exitCode !== 0) {
|
|
113
124
|
const conflictFiles = parseConflicts(result.stderr);
|
|
114
125
|
// Lockfile-only conflicts can be auto-resolved by regenerating.
|
|
115
|
-
if (await this.
|
|
126
|
+
if (await this.tryAutoResolveConflict(wtPath)) {
|
|
116
127
|
const sha = (await this.gitIn(wtPath, ["rev-parse", "HEAD"])).stdout.trim();
|
|
117
128
|
await this.git(["worktree", "remove", "--force", wtPath], { allowNonZero: true });
|
|
118
129
|
return { success: true, sha };
|
|
@@ -128,17 +139,18 @@ export class ShellGitOperations {
|
|
|
128
139
|
return { success: true, sha };
|
|
129
140
|
}
|
|
130
141
|
/**
|
|
131
|
-
* During a merge conflict, check if
|
|
132
|
-
* If so,
|
|
133
|
-
* appropriate package manager.
|
|
142
|
+
* During a merge conflict, check if all unmerged files match a configured
|
|
143
|
+
* auto-resolve pattern. If so, regenerate them via the pattern's command.
|
|
134
144
|
*/
|
|
135
|
-
async
|
|
145
|
+
async tryAutoResolveConflict(wtPath) {
|
|
146
|
+
if (this.autoResolvePatterns.length === 0)
|
|
147
|
+
return false;
|
|
136
148
|
try {
|
|
137
149
|
const unmerged = await this.gitIn(wtPath, ["diff", "--name-only", "--diff-filter=U"]);
|
|
138
150
|
const files = unmerged.stdout.trim().split("\n").filter(Boolean);
|
|
139
151
|
if (files.length === 0)
|
|
140
152
|
return false;
|
|
141
|
-
const regenerate =
|
|
153
|
+
const regenerate = detectAutoResolveCommand(files, this.autoResolvePatterns);
|
|
142
154
|
if (!regenerate)
|
|
143
155
|
return false;
|
|
144
156
|
for (const file of files) {
|
package/dist/server.js
CHANGED
|
@@ -24,6 +24,8 @@ async function createRepoInstance(config, logger, botIdentity) {
|
|
|
24
24
|
const git = new ShellGitOperations(clone.path, config.repoFullName, config.gitBin);
|
|
25
25
|
if (botIdentity)
|
|
26
26
|
git.setBotIdentity(botIdentity);
|
|
27
|
+
if (config.autoResolvePatterns.length > 0)
|
|
28
|
+
git.setAutoResolvePatterns(config.autoResolvePatterns);
|
|
27
29
|
const ci = new GitHubActionsRunner(config.repoFullName, config.requiredChecks);
|
|
28
30
|
const github = new GitHubPRClient(config.repoFullName);
|
|
29
31
|
const eviction = new GitHubCheckRunReporter(config.repoFullName, config.server.bind, config.server.port, config.server.publicBaseUrl, config.admissionLabel, config.mergeQueueCheckName);
|